/**
 * Voice dictation button + recording overlay for the agent composer.
 *
 * UX mirrors Lovable: click-to-toggle record, a live amplitude bar and
 * MM:SS timer replace the editor area while recording, and a cancel X
 * discards without transcribing. The mic is always visible alongside the
 * send button (Cursor replaces send with mic; their users complain — we
 * don't copy that).
 */

import {
  IconMicrophone,
  IconPlayerStopFilled,
  IconLoader2,
  IconX,
} from "@tabler/icons-react";
import { useCallback, useEffect, useState } from "react";

import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
import { RealtimeVoiceModeEntry } from "./RealtimeVoiceMode.js";
import type { RealtimeVoiceInputMode } from "./RealtimeVoiceMode.js";
import { useComposerRuntimeAdapters } from "./runtime-adapters.js";
import {
  useRealtimeVoiceModeCopy,
  useRealtimeVoiceModeOptional,
} from "./useRealtimeVoiceMode.js";
import type { VoiceDictationApi } from "./useVoiceDictation.js";

const VOICE_INPUT_PREFERENCE_KEY = "voice-input-preference";

export function normalizeVoiceInputPreference(
  value: unknown,
): RealtimeVoiceInputMode | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const mode = (value as { mode?: unknown }).mode;
  return mode === "realtime" || mode === "dictation" ? mode : null;
}

function openOpenAiKeySettings(): void {
  window.location.hash = "#secrets:OPENAI_API_KEY";
  window.dispatchEvent(new Event("agent-panel:open"));
  window.dispatchEvent(
    new CustomEvent("agent-panel:open-settings", {
      detail: { section: "secrets" },
    }),
  );
}

export interface VoiceButtonProps {
  voice: VoiceDictationApi;
  isMac: boolean;
  disabled?: boolean;
}

export function isRealtimeVoiceSetupRequired(
  status: { builder?: boolean; openai?: boolean } | null,
  builderConfigured: boolean | null,
): boolean {
  return (
    status !== null &&
    !status.builder &&
    !status.openai &&
    builderConfigured !== true
  );
}

export function VoiceButton({ voice, isMac, disabled }: VoiceButtonProps) {
  const adapters = useComposerRuntimeAdapters();
  const { state, start, stop, supported } = voice;
  const realtimeVoice = useRealtimeVoiceModeOptional();
  const realtimeCopy = useRealtimeVoiceModeCopy();
  const voiceProviders = adapters.voice!.useProviderStatus!();
  const builderConnect = adapters.builder!.useConnectFlow!({
    trackingSource: "realtime_voice",
    trackingFlow: "voice_transcription",
    onConnected: () => voiceProviders.refresh(),
  });
  const [inputPreference, setInputPreference] = useState<{
    ready: boolean;
    mode: RealtimeVoiceInputMode | null;
  }>({ ready: false, mode: null });

  useEffect(() => {
    let cancelled = false;
    void Promise.resolve(
      adapters.voice!.readAppState!(VOICE_INPUT_PREFERENCE_KEY),
    )
      .then((value) => {
        if (!cancelled) {
          setInputPreference({
            ready: true,
            mode: normalizeVoiceInputPreference(value),
          });
        }
      })
      .catch(() => {
        if (!cancelled) setInputPreference({ ready: true, mode: null });
      });
    return () => {
      cancelled = true;
    };
  }, [adapters]);

  const rememberInputPreference = useCallback(
    (mode: RealtimeVoiceInputMode) => {
      setInputPreference({ ready: true, mode });
      void Promise.resolve(
        adapters.voice!.setAppState!(VOICE_INPUT_PREFERENCE_KEY, { mode }),
      ).catch(() => undefined);
    },
    [adapters],
  );

  if (!supported) return null;

  const recording = state === "recording" || state === "starting";
  const transcribing = state === "transcribing";

  if (realtimeVoice?.active && !recording && !transcribing) return null;

  if (realtimeVoice && !recording && !transcribing) {
    return (
      <RealtimeVoiceModeEntry
        copy={realtimeCopy}
        disabled={disabled}
        providerStatusPending={voiceProviders.status === null}
        setupRequired={isRealtimeVoiceSetupRequired(
          voiceProviders.status,
          builderConnect.statusResolved ? builderConnect.configured : null,
        )}
        openAiConfigured={voiceProviders.status?.openai === true}
        connectingBuilder={builderConnect.connecting}
        onConnectBuilder={builderConnect.start}
        onUseOpenAiKey={() => {
          if (voiceProviders.status?.openai) void realtimeVoice.start();
          else openOpenAiKeySettings();
        }}
        onStartVoiceMode={() => void realtimeVoice.start()}
        onKeepDictating={() => void start()}
        preferredMode={inputPreference.ready ? inputPreference.mode : null}
        onRememberPreference={rememberInputPreference}
      />
    );
  }

  const label = recording
    ? "Stop recording"
    : transcribing
      ? "Transcribing…"
      : `Dictate (${isMac ? "⌘⇧M" : "Ctrl+Shift+M"})`;

  const onClick = () => {
    if (recording) stop();
    else if (!transcribing) void start();
  };

  return (
    <Tooltip>
      <TooltipTrigger asChild>
        <button
          type="button"
          onClick={onClick}
          disabled={disabled || transcribing}
          aria-label={label}
          aria-pressed={recording}
          className={`shrink-0 flex h-7 w-7 items-center justify-center rounded-md transition-colors duration-150 disabled:opacity-30 disabled:cursor-not-allowed ${
            recording
              ? "text-[#00B5FF] bg-[#00B5FF]/10 hover:bg-[#00B5FF]/20"
              : "text-muted-foreground hover:text-foreground hover:bg-accent/50"
          }`}
        >
          {transcribing ? (
            <IconLoader2 className="h-4 w-4 animate-spin" />
          ) : recording ? (
            <IconPlayerStopFilled className="h-3.5 w-3.5" />
          ) : (
            <IconMicrophone className="h-4 w-4" />
          )}
        </button>
      </TooltipTrigger>
      <TooltipContent>{label}</TooltipContent>
    </Tooltip>
  );
}

export interface VoiceRecordingOverlayProps {
  voice: VoiceDictationApi;
}

export function VoiceRecordingOverlay({ voice }: VoiceRecordingOverlayProps) {
  const { state, amplitude, durationMs, errorMessage, cancel } = voice;
  const { dismissError, start } = voice;

  if (state === "error" && errorMessage) {
    return (
      <div
        role="alert"
        className="mx-2 mt-1 flex items-start gap-2 rounded-md border border-red-500/40 bg-red-500/10 px-2 py-1.5 text-[11px] text-red-500"
      >
        <span className="flex-1 min-w-0">{errorMessage}</span>
        <Tooltip>
          <TooltipTrigger asChild>
            <button
              type="button"
              onClick={() => {
                dismissError();
                void start();
              }}
              className="shrink-0 cursor-pointer rounded px-1.5 py-0.5 text-[11px] font-medium text-red-500 hover:bg-red-500/20"
              aria-label="Try again"
            >
              Try again
            </button>
          </TooltipTrigger>
          <TooltipContent>Try again</TooltipContent>
        </Tooltip>
        <Tooltip>
          <TooltipTrigger asChild>
            <button
              type="button"
              onClick={dismissError}
              className="shrink-0 flex h-4 w-4 cursor-pointer items-center justify-center rounded text-red-500 hover:bg-red-500/20"
              aria-label="Dismiss"
            >
              <IconX className="h-3 w-3" />
            </button>
          </TooltipTrigger>
          <TooltipContent>Dismiss</TooltipContent>
        </Tooltip>
      </div>
    );
  }

  if (state !== "recording" && state !== "starting" && state !== "transcribing")
    return null;

  return (
    <div
      className="flex items-center gap-2 mx-2 mt-2 mb-1 h-[2rem] rounded-md border border-[#00B5FF]/40 bg-[#00B5FF]/10 px-2"
      aria-live="polite"
    >
      <Tooltip>
        <TooltipTrigger asChild>
          <button
            type="button"
            onClick={cancel}
            className="shrink-0 flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/40"
            aria-label="Cancel recording"
          >
            <IconX className="h-3 w-3" />
          </button>
        </TooltipTrigger>
        <TooltipContent>Cancel (Esc)</TooltipContent>
      </Tooltip>

      <div className="flex-1 flex items-center gap-[2px] min-w-0 h-4">
        {state === "transcribing" ? (
          <span className="text-[11px] text-muted-foreground">
            Transcribing…
          </span>
        ) : (
          <AmplitudeBars amplitude={amplitude} />
        )}
      </div>

      <span className="shrink-0 text-[11px] font-medium tabular-nums text-muted-foreground">
        {state === "transcribing" ? (
          <IconLoader2 className="h-3 w-3 animate-spin" />
        ) : (
          formatDuration(durationMs)
        )}
      </span>
    </div>
  );
}

const BAR_COUNT = 24;

function AmplitudeBars({ amplitude }: { amplitude: number }) {
  // Render a symmetric meter — the middle bars peak first so the visual
  // matches what voice input looks like in Lovable / iOS dictation.
  const bars = [];
  for (let i = 0; i < BAR_COUNT; i++) {
    const centerDistance =
      Math.abs(i - (BAR_COUNT - 1) / 2) / ((BAR_COUNT - 1) / 2);
    const heightPct =
      Math.max(0.1, amplitude * (1 - centerDistance * 0.6)) * 100;
    bars.push(
      <span
        key={i}
        className="flex-1 rounded-full bg-[#00B5FF]"
        style={{ height: `${heightPct}%`, minHeight: 2 }}
      />,
    );
  }
  return <>{bars}</>;
}

function formatDuration(ms: number): string {
  const total = Math.floor(ms / 1000);
  const m = Math.floor(total / 60);
  const s = total % 60;
  return `${m}:${s.toString().padStart(2, "0")}`;
}
