import { For, Show, createEffect, createSignal, onCleanup, type Component } from "solid-js";
import { runBackendAction, setBackendWeight, type BackendAction } from "../api/admin";
import Modal from "./Modal";
import { applyConfiguredWeight } from "../stores/state";

type Props = {
  maglevd: string;
  frontend: string;
  pool: string;
  backend: string;
  state: string;
  // Current configured weight. Used to seed the weight dialog's
  // number input so the operator sees the existing value and only
  // has to change the digits that matter.
  configuredWeight: number;
};

// MenuAction is what the user clicks. It maps 1:1 onto BackendAction
// plus a virtual "weight" entry that opens the weight form rather
// than firing an immediate lifecycle RPC.
type MenuAction = BackendAction | "weight";

type MenuItem = {
  label: string;
  action: MenuAction;
};

// Available items per state. "weight" is always present (operators
// can adjust the configured weight in any state including paused
// and disabled — the effective weight won't change until the state
// lets it, but the config value is still meaningful). A removed
// backend has no actionable operations, so the kebab is suppressed
// entirely by returning an empty list.
function itemsForState(state: string): MenuItem[] {
  const weightItem: MenuItem = { label: "set weight…", action: "weight" };
  switch (state) {
    case "up":
    case "down":
    case "unknown":
      return [
        weightItem,
        { label: "pause", action: "pause" },
        { label: "disable", action: "disable" },
      ];
    case "paused":
      return [
        weightItem,
        { label: "resume", action: "resume" },
        { label: "disable", action: "disable" },
      ];
    case "disabled":
      return [weightItem, { label: "enable", action: "enable" }];
    default:
      return [];
  }
}

// consequenceText returns the "this will…" description shown in the
// confirmation dialog for each lifecycle action. Spelling it out in
// plain English makes the live-traffic impact unmistakable before
// the operator commits.
function consequenceText(action: BackendAction): string {
  switch (action) {
    case "pause":
      return "This will stop health checks and set the weight to 0, but existing flows to this backend are kept. New traffic will be rerouted to other backends.";
    case "resume":
      return "This will restart health checks. The backend re-enters the 'unknown' state and will start receiving traffic once it probes up.";
    case "disable":
      return "This will stop health checks, set the weight to 0, AND flush VPP's flow table for this backend. Active sessions will be dropped immediately.";
    case "enable":
      return "This will restart health checks on a previously disabled backend. It re-enters the 'unknown' state and will start receiving traffic once it probes up.";
  }
}

const BackendActionsMenu: Component<Props> = (props) => {
  const [open, setOpen] = createSignal(false);
  const [dialog, setDialog] = createSignal<MenuAction | undefined>();
  const [busy, setBusy] = createSignal(false);
  const [error, setError] = createSignal<string | undefined>();

  // Weight-dialog form state. Seeded from the current configured
  // weight each time the dialog opens so re-opening after a change
  // shows the new value.
  const [weightInput, setWeightInput] = createSignal(props.configuredWeight);
  const [flushInput, setFlushInput] = createSignal(false);

  let wrapRef: HTMLDivElement | undefined;

  const items = () => itemsForState(props.state);

  // Close the popover on outside click or Escape while it's open.
  // The dialog has its own Escape handler; this effect only runs
  // when the kebab popover (not the dialog) is visible.
  createEffect(() => {
    if (!open()) return;
    const onMouseDown = (e: MouseEvent) => {
      if (!wrapRef) return;
      if (!wrapRef.contains(e.target as Node)) setOpen(false);
    };
    const onKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") setOpen(false);
    };
    document.addEventListener("mousedown", onMouseDown);
    document.addEventListener("keydown", onKey);
    onCleanup(() => {
      document.removeEventListener("mousedown", onMouseDown);
      document.removeEventListener("keydown", onKey);
    });
  });

  const openDialog = (action: MenuAction) => {
    setOpen(false);
    setError(undefined);
    if (action === "weight") {
      // Seed form from current value on each open.
      setWeightInput(props.configuredWeight);
      setFlushInput(false);
    }
    setDialog(action);
  };

  const closeDialog = () => {
    if (busy()) return; // don't yank the modal out from under an in-flight call
    setDialog(undefined);
    setError(undefined);
  };

  const commit = async () => {
    const action = dialog();
    if (!action) return;
    setBusy(true);
    setError(undefined);
    try {
      if (action === "weight") {
        const w = weightInput();
        if (!Number.isFinite(w) || w < 0 || w > 100) {
          throw new Error("weight must be an integer in [0, 100]");
        }
        const newWeight = Math.floor(w);
        await setBackendWeight(
          props.maglevd,
          props.frontend,
          props.pool,
          props.backend,
          newWeight,
          flushInput(),
        );
        // Mirror the server-side mutation into our local store so
        // the new weight (and any resulting effective-weight
        // recompute) is visible instantly, without waiting for
        // the next 30s refresh tick.
        applyConfiguredWeight(props.maglevd, props.frontend, props.pool, props.backend, newWeight);
      } else {
        await runBackendAction(props.maglevd, props.backend, action);
      }
      setDialog(undefined);
    } catch (err) {
      setError(`${err}`);
    } finally {
      setBusy(false);
    }
  };

  return (
    <Show when={items().length > 0}>
      <div class="kebab-wrap" ref={wrapRef}>
        <button
          type="button"
          class="kebab-btn"
          aria-haspopup="menu"
          aria-expanded={open()}
          title="backend actions"
          onClick={(e) => {
            e.stopPropagation();
            setOpen((v) => !v);
          }}
        >
          {"\u22EE"}
        </button>
        <Show when={open()}>
          <div class="kebab-menu" role="menu">
            <For each={items()}>
              {(item) => (
                <button
                  type="button"
                  class="kebab-item"
                  role="menuitem"
                  onClick={() => openDialog(item.action)}
                >
                  {item.label}
                </button>
              )}
            </For>
          </div>
        </Show>

        <Show when={dialog()}>
          {(action) => (
            <Modal title={dialogTitle(action(), props.backend)} onClose={closeDialog}>
              {action() === "weight" ? (
                <div class="dialog-body">
                  <p class="dialog-target">
                    <code>{props.backend}</code> in pool <code>{props.pool}</code> of frontend{" "}
                    <code>{props.frontend}</code>
                  </p>
                  <label class="dialog-field">
                    <span class="weight-slider-label">
                      weight
                      <output class="weight-slider-value">{weightInput()}</output>
                    </span>
                    <input
                      type="range"
                      class="weight-slider"
                      min="0"
                      max="100"
                      step="1"
                      value={weightInput()}
                      onInput={(e) => setWeightInput(Number(e.currentTarget.value))}
                    />
                    <small>0–100; 0 keeps the backend in the pool but assigns it no traffic</small>
                  </label>
                  <label class="dialog-field checkbox">
                    <input
                      type="checkbox"
                      checked={flushInput()}
                      onChange={(e) => setFlushInput(e.currentTarget.checked)}
                    />
                    <span>flush existing flows</span>
                  </label>
                  <Show
                    when={flushInput()}
                    fallback={
                      <p class="dialog-note">
                        VPP's flow table is left alone. Existing sessions keep reaching this backend
                        until they finish.
                      </p>
                    }
                  >
                    <p class="dialog-warn">
                      VPP's flow table will be cleared for this backend. Active sessions will be
                      dropped immediately.
                    </p>
                  </Show>
                </div>
              ) : (
                <div class="dialog-body">
                  <p class="dialog-consequence">{consequenceText(action() as BackendAction)}</p>
                </div>
              )}

              <Show when={error()}>
                <p class="dialog-error">{error()}</p>
              </Show>

              <footer class="dialog-footer">
                <button type="button" class="btn-secondary" onClick={closeDialog} disabled={busy()}>
                  cancel
                </button>
                <button
                  type="button"
                  classList={{
                    "btn-primary": true,
                    "btn-danger": action() === "disable" || (action() === "weight" && flushInput()),
                  }}
                  onClick={commit}
                  disabled={busy()}
                >
                  {busy() ? "committing…" : "commit"}
                </button>
              </footer>
            </Modal>
          )}
        </Show>
      </div>
    </Show>
  );
};

function dialogTitle(action: MenuAction, backend: string): string {
  switch (action) {
    case "weight":
      return `Set weight — ${backend}`;
    case "pause":
      return `Pause ${backend}?`;
    case "resume":
      return `Resume ${backend}?`;
    case "disable":
      return `Disable ${backend}?`;
    case "enable":
      return `Enable ${backend}?`;
  }
}

export default BackendActionsMenu;
