import { PROFILE_LABELS } from "../../core/gamepadProfile.js";

const PROFILE_BADGE_LABELS = {
  xbox: "Xbox",
  playstation: "PS",
  switch: "Switch",
};

const LOCK_TEMPLATE = `
<section class="view lock-view" data-view="lock">
  <section class="lock-layout panel">
    <section class="lock-left">
      <div class="lock-user">
        <span class="lock-avatar" aria-hidden="true"></span>
        <p class="lock-username" data-username>Nebula User</p>
      </div>
      <div class="lock-title-row">
        <h1 class="lock-title">Enter your passkey</h1>
        <span class="lock-controller-badge" data-controller-badge hidden aria-hidden="true"></span>
      </div>
      <p class="lock-copy" data-copy>Using your controller, enter your 6-digit passkey.</p>
      <div class="lock-dots" data-passkey-dots></div>
      <p class="lock-status" data-status aria-live="polite"></p>
    </section>

    <section class="lock-right panel" data-focus-root>
      <button class="focusable lock-num" data-focusable="true" data-row="0" data-col="0" data-digit="1" data-focus-key="digit-1">1 <span class="lock-map" data-map="up"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="0" data-col="1" data-digit="2" data-focus-key="digit-2">2 <span class="lock-map" data-map="left"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="0" data-col="2" data-digit="3" data-focus-key="digit-3">3 <span class="lock-map" data-map="down"></span></button>

      <button class="focusable lock-num" data-focusable="true" data-row="1" data-col="0" data-digit="4" data-focus-key="digit-4">4 <span class="lock-map" data-map="right"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="1" data-col="1" data-digit="5" data-focus-key="digit-5">5 <span class="lock-map" data-map="l2"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="1" data-col="2" data-digit="6" data-focus-key="digit-6">6 <span class="lock-map" data-map="r2"></span></button>

      <button class="focusable lock-num" data-focusable="true" data-row="2" data-col="0" data-digit="7" data-focus-key="digit-7">7 <span class="lock-map" data-map="l1"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="2" data-col="1" data-digit="8" data-focus-key="digit-8">8 <span class="lock-map" data-map="r1"></span></button>
      <button class="focusable lock-num" data-focusable="true" data-row="2" data-col="2" data-digit="9" data-focus-key="digit-9">9 <span class="lock-map" data-map="y"></span></button>

      <button class="focusable lock-num is-zero" data-focusable="true" data-row="3" data-col="1" data-digit="0" data-focus-key="digit-0">0 <span class="lock-map" data-map="clear"></span></button>
    </section>
  </section>
</section>
`;

export const createLockView = ({ state, renderView, keyboard }) => {
  const ENTRY_DEBOUNCE_MS = 120;
  const FAIL_CLEAR_MS = 600;

  let digits = [];
  let setupDigits = [];
  let setupSeed = "";
  let setupPhase = "create";
  let busy = false;
  let lastEntryAt = 0;
  let keyboardListener = null;
  let profileListener = null;
  let awaitingConfirm = false;

  const config = () => state.passkey.getConfig();

  const controllerLabel = () => PROFILE_LABELS[state.controllerProfile] ?? PROFILE_LABELS.generic;

  const menuGlyphMarkup = () => {
    const glyph = state.glyphs.menu ?? "Menu";
    return `<span class="lock-glyph lock-glyph-menu" aria-hidden="true">${glyph}</span>`;
  };

  const menuConfirmMarkup = (prefix = "Press ", suffix = " to confirm.") =>
    `${prefix}${menuGlyphMarkup()}${suffix}`;

  const ACTION_TO_DIGIT = {
    up: "1",
    left: "2",
    down: "3",
    right: "4",
    l2: "5",
    r2: "6",
    l1: "7",
    r1: "8",
    y: "9",
    clear: "0",
  };

  const playTone = (frequency = 860, durationMs = 34, gainValue = 0.03) => {
    const AudioContextClass = window.AudioContext ?? window.webkitAudioContext;
    if (!AudioContextClass) {
      return;
    }

    try {
      const ctx = new AudioContextClass();
      const oscillator = ctx.createOscillator();
      const gain = ctx.createGain();
      oscillator.type = "triangle";
      oscillator.frequency.value = frequency;
      gain.gain.value = gainValue;
      oscillator.connect(gain);
      gain.connect(ctx.destination);
      oscillator.start();

      window.setTimeout(() => {
        oscillator.stop();
        ctx.close();
      }, durationMs);
    } catch (_error) {
      // no-op
    }
  };

  const pulseHaptic = (ms = 10) => {
    navigator.vibrate?.(ms);
  };

  const setStatus = (text = "", danger = false, { html = false } = {}) => {
    const status = document.querySelector("[data-status]");
    if (!status) {
      return;
    }

    if (html) {
      status.innerHTML = text;
    } else {
      status.textContent = text;
    }
    status.classList.toggle("is-danger", danger);
  };

  const setConfirmStatus = () => {
    awaitingConfirm = true;
    setStatus(menuConfirmMarkup(), false, { html: true });
  };

  const updateControllerBadge = () => {
    const badge = document.querySelector("[data-controller-badge]");
    if (!badge) {
      return;
    }

    const profile = state.controllerProfile ?? "generic";
    badge.hidden = profile === "generic";
    badge.className = `lock-controller-badge lock-controller-badge--${profile}`;
    badge.textContent = PROFILE_BADGE_LABELS[profile] ?? state.glyphs.menu ?? "";
    badge.title = `${controllerLabel()} controller detected`;
  };

  const updateCopy = () => {
    const copy = document.querySelector("[data-copy]");
    if (!copy) {
      return;
    }

    const length = config().length;
    const label = controllerLabel();

    if (state.passkeySetupRequired) {
      if (setupPhase === "confirm") {
        copy.innerHTML = `Re-enter the same ${length}-digit passkey, then ${menuConfirmMarkup("press ", ".")}`;
        return;
      }
      copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`;
      return;
    }

    if (config().requireConfirm) {
      copy.innerHTML = `Use your ${label} controller buttons to enter ${length} digits, then ${menuConfirmMarkup("press ", ".")}`;
      return;
    }

    copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`;
  };

  const updateMapLabels = () => {
    document.querySelectorAll("[data-map]").forEach((element) => {
      const action = element.dataset.map;
      element.textContent = state.glyphs[action] ?? action?.toUpperCase?.() ?? "";
    });
  };

  const renderDots = (stateClass = "") => {
    const dots = document.querySelector("[data-passkey-dots]");
    if (!dots) {
      return;
    }

    dots.className = `lock-dots ${stateClass}`.trim();
    dots.innerHTML = "";

    for (let index = 0; index < config().length; index += 1) {
      const dot = document.createElement("span");
      dot.className = "lock-dot";

      if (index < digits.length) {
        dot.classList.add("filled");
      }
      if (index === digits.length && digits.length < config().length) {
        dot.classList.add("active");
      }

      dots.append(dot);
    }
  };

  const clearDigits = () => {
    digits = [];
    awaitingConfirm = false;
    renderDots();
  };

  const lockoutText = (remainingMs) => `Too many attempts. Retry in ${Math.ceil(remainingMs / 1000)}s.`;

  const applyFailure = (text) => {
    setStatus(text, true);
    renderDots("is-error is-shaking");
    playTone(220, 84, 0.05);

    window.setTimeout(() => {
      clearDigits();
    }, FAIL_CLEAR_MS);
  };

  const applySuccess = () => {
    const view = document.querySelector(".lock-view");
    setStatus("", false);
    renderDots("is-success");
    view?.classList.add("is-success");
    playTone(1200, 66, 0.04);
    pulseHaptic(24);

    window.setTimeout(() => {
      state.locked = false;
      state.activeView = "home";
      renderView("home");
    }, 240);
  };

  const submitDigits = async () => {
    if (busy) {
      return;
    }

    if (digits.length !== config().length) {
      setStatus(`Enter ${config().length} digits.`, true);
      return;
    }

    busy = true;

    if (!config().enabled) {
      applySuccess();
      busy = false;
      return;
    }

    if (state.passkeySetupRequired) {
      if (setupPhase === "create") {
        setupDigits = [...digits];
        setupSeed = setupDigits.join("|");
        clearDigits();
        setupPhase = "confirm";
        updateCopy();
        setConfirmStatus();
        busy = false;
        return;
      }

      const confirmSeed = digits.join("|");
      const same = setupSeed.length > 0
        && setupDigits.length === digits.length
        && setupDigits.every((digit, index) => digit === digits[index])
        && confirmSeed === setupSeed;
      if (!same) {
        setupPhase = "create";
        setupDigits = [];
        setupSeed = "";
        updateCopy();
        applyFailure("Passkeys did not match. Try again.");
        busy = false;
        return;
      }

      await state.passkey.setSequence(setupDigits);
      state.passkeySetupRequired = false;
      applySuccess();
      busy = false;
      return;
    }

    const result = await state.passkey.verifySequence(digits);
    if (result.ok) {
      applySuccess();
      busy = false;
      return;
    }

    if (result.reason === "lockout") {
      applyFailure(lockoutText(result.lockoutRemainingMs ?? state.passkey.getLockoutRemainingMs()));
      busy = false;
      return;
    }

    if (result.reason === "setup-required") {
      state.passkeySetupRequired = true;
      setupPhase = "create";
      setupDigits = [];
      updateCopy();
      applyFailure("Passkey setup required.");
      busy = false;
      return;
    }

    applyFailure(`Incorrect passkey. ${result.attemptsLeft ?? 0} attempts left.`);
    busy = false;
  };

  const pushDigit = (digit) => {
    if (busy || state.passkey.inLockout()) {
      if (state.passkey.inLockout()) {
        setStatus(lockoutText(state.passkey.getLockoutRemainingMs()), true);
      }
      return;
    }

    const now = performance.now();
    if (now - lastEntryAt < ENTRY_DEBOUNCE_MS) {
      return;
    }
    lastEntryAt = now;

    if (digits.length >= config().length) {
      return;
    }

    digits.push(String(digit));
    renderDots();
    setStatus("");
    playTone();
    pulseHaptic(8);

    if (digits.length === config().length && !config().requireConfirm) {
      if (state.passkeySetupRequired) {
        setConfirmStatus();
      } else {
        submitDigits();
      }
    } else if (digits.length === config().length && config().requireConfirm) {
      setConfirmStatus();
    }
  };

  const deleteLast = () => {
    if (!digits.length || busy) {
      return;
    }
    digits = digits.slice(0, -1);
    awaitingConfirm = false;
    renderDots();
    setStatus("");
  };

  const clearAll = () => {
    if (!digits.length || busy) {
      return;
    }
    clearDigits();
    setStatus("");
  };

  const handleNumberKey = (event) => {
    if (!config().keyboardSupport || !state.locked) {
      return;
    }

    const digitMatch = event.code.match(/^(Digit|Numpad)(\d)$/);
    if (!digitMatch) {
      return;
    }

    event.preventDefault();
    pushDigit(digitMatch[2]);
  };

  return {
    id: "lock",
    render: () => LOCK_TEMPLATE,
    mount: () => {
      keyboard?.close?.();
      if (state.userSetupRequired) {
        renderView("user-setup");
        return;
      }

      setupPhase = "create";
      setupDigits = [];
      setupSeed = "";
      clearDigits();
      updateCopy();
      setStatus("");

      const username = document.querySelector("[data-username]");
      if (username) {
        username.textContent = state.profileName ?? "Nebula User";
      }

      document.documentElement.style.setProperty("--nebula-focus-strength", "1");
      document.documentElement.dataset.animSpeed = config().animationSpeed;
      document.documentElement.classList.toggle("high-contrast", config().highContrast);
      updateMapLabels();
      updateControllerBadge();
      state.refreshControllerGlyphs?.();

      if (keyboardListener) {
        window.removeEventListener("keydown", keyboardListener);
      }
      keyboardListener = handleNumberKey;
      window.addEventListener("keydown", keyboardListener);

      if (profileListener) {
        window.removeEventListener("nebula-controller-profile", profileListener);
      }
      profileListener = () => {
        updateMapLabels();
        updateCopy();
        updateControllerBadge();
        if (awaitingConfirm) {
          setConfirmStatus();
        }
        if (state.activeView === "lock") {
          window.dispatchEvent(new CustomEvent("nebula-navigation-refresh"));
        }
      };
      window.addEventListener("nebula-controller-profile", profileListener);
    },
    getNavigationContract: () => {
      const root = document.querySelector("[data-focus-root]");
      const defaultFocus = root?.querySelector("[data-digit='5']") ?? root?.querySelector("[data-digit='1']") ?? null;

      return {
        focusRoot: root,
        defaultFocus,
        layout: { type: "grid", cols: 3, rows: 4 },
        hintsTemplate: "#lock-hints-template",
        nebulaNavigation: state.nebula.navigation,
        captureDirectionalInput: true,
        onAccept: (element) => {
          const digit = element?.dataset.digit;
          if (!digit) {
            return;
          }
          pushDigit(digit);
        },
        onBack: () => {
          deleteLast();
        },
        onMenu: () => {
          if (digits.length === config().length) {
            submitDigits();
            return;
          }
          setStatus(`Enter ${config().length} digits first.`, true);
        },
        onAction: (action) => {
          const mappedDigit = ACTION_TO_DIGIT[action];
          if (mappedDigit) {
            pushDigit(mappedDigit);
            return true;
          }

          return false;
        },
      };
    },
  };
};
