import { useCoreServices } from '@learnitive/context';
import {
  DEFAULT_OLLAMA_BASE_URL,
  LOCAL_AI_MODELS,
  defaultProjectAiModel,
  type LocalAiModelId,
  type ProjectAiProviderId,
  type ProjectAiProviderMode,
} from '@learnitive/project-ai';
import {
  Button,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  Input,
  Label,
  toast,
} from '@learnitive/ui-components';
import { useAtomValue } from 'jotai';
import { CircleAlert, Download, ExternalLink, HardDrive, KeyRound, LoaderCircle, RefreshCw, Server, Settings2, Sparkles } from 'lucide-react';
import React from 'react';
import { AiProviderModelSelect } from './ai-provider-model-select';
import { AI_SETTINGS_OPEN_EVENT, notifyAiSettingsClosed, notifyAiSettingsSaved, openAiSettingsDialog } from './ai-settings-events';
import { AI_LARGE_DIALOG_CONTENT_CLASS } from './ai-modal-layout';

const CLOUD_PROVIDERS: ReadonlyArray<{ id: ProjectAiProviderId; label: string }> = [
  { id: 'openai', label: 'OpenAI' },
  { id: 'anthropic', label: 'Anthropic' },
  { id: 'gemini', label: 'Gemini' },
  { id: 'openrouter', label: 'OpenRouter' },
  { id: 'openai-compatible', label: 'OpenAI-compatible' },
];

function isCloudProvider(provider: ProjectAiProviderMode): provider is ProjectAiProviderId {
  return provider !== 'auto' && provider !== 'local' && provider !== 'ollama' && provider !== 'disabled';
}

function formatBytes(bytes?: number): string {
  if (!bytes || bytes <= 0) return '—';
  const units = ['B', 'KB', 'MB', 'GB'];
  let value = bytes;
  let index = 0;
  while (value >= 1024 && index < units.length - 1) {
    value /= 1024;
    index += 1;
  }
  return `${value >= 100 || index === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[index]}`;
}

/**
 * Always-available editor-titlebar AI setup. This intentionally configures the
 * same ProjectAiService used by AI Suggest and Paper Review so there is no
 * second credential/model store. BYOK keys remain browser-local in IndexedDB.
 */
export function AiSettingsControl({ mode = 'both' }: { mode?: 'both' | 'trigger' | 'dialog' } = {}) {
  const { projectAi, projectAiWriting } = useCoreServices();
  const aiSettings = useAtomValue(projectAi.$settings);
  const aiWritingSettings = useAtomValue(projectAiWriting.$settings);
  const automaticWatcherStatus = useAtomValue(projectAiWriting.$automaticStatus);
  const activeRequests = useAtomValue(projectAi.$activeRequests);
  const credentialsHydrated = useAtomValue(projectAi.$credentialsHydrated);
  const localAi = useAtomValue(projectAi.$localAi);
  const ollama = useAtomValue(projectAi.$ollama);
  const [open, setOpen] = React.useState(false);
  const [provider, setProvider] = React.useState<ProjectAiProviderMode>(aiSettings.provider);
  const [localModelId, setLocalModelId] = React.useState<LocalAiModelId>(aiSettings.localModelId);
  const [model, setModel] = React.useState(aiSettings.model);
  const [baseUrl, setBaseUrl] = React.useState(aiSettings.baseUrl);
  const [apiKey, setApiKey] = React.useState(() => projectAi.getApiKeyForDisplay());
  const [privacyMode, setPrivacyMode] = React.useState(aiSettings.privacyMode);

  React.useEffect(() => {
    if (mode === 'trigger') return undefined;
    const handleOpen = () => setOpen(true);
    window.addEventListener(AI_SETTINGS_OPEN_EVENT, handleOpen);
    return () => window.removeEventListener(AI_SETTINGS_OPEN_EVENT, handleOpen);
  }, [mode]);

  React.useEffect(() => {
    if (!open) return;
    setProvider(aiSettings.provider === 'disabled' ? 'local' : aiSettings.provider);
    setLocalModelId(aiSettings.localModelId);
    const effectiveProvider = aiSettings.provider === 'disabled' ? 'local' : aiSettings.provider;
    setModel(aiSettings.model || (isCloudProvider(effectiveProvider) ? defaultProjectAiModel(effectiveProvider) : ''));
    setBaseUrl(effectiveProvider === 'ollama' ? (aiSettings.baseUrl || DEFAULT_OLLAMA_BASE_URL) : aiSettings.baseUrl);
    setApiKey(projectAi.getApiKeyForDisplay());
    setPrivacyMode(aiSettings.privacyMode);
  }, [aiSettings, open, projectAi]);

  React.useEffect(() => {
    if (!open) return;
    void projectAi.refreshLocalAiState(localModelId).catch(() => undefined);
  }, [localModelId, open, projectAi]);

  React.useEffect(() => {
    if (!open || !credentialsHydrated || !isCloudProvider(provider)) return;
    setApiKey(projectAi.getApiKeyForProvider(provider, baseUrl));
  }, [baseUrl, credentialsHydrated, open, projectAi, provider]);

  const selectedLocalModel = LOCAL_AI_MODELS.find((item) => item.id === localModelId) ?? LOCAL_AI_MODELS[0];
  const localState = localAi.model?.modelId === localModelId ? localAi.model : undefined;
  const localInstalled = localState?.status === 'installed' || localState?.status === 'ready';
  const downloading = localAi.status === 'downloading' && localState?.modelId === localModelId;
  const progress = downloading && localState?.totalBytes
    ? Math.min(100, Math.round(((localState.bytesDownloaded ?? 0) / localState.totalBytes) * 100))
    : undefined;
  const localOllamaModels = ollama.models.filter((item) => !item.remote);
  const ollamaCloudModels = ollama.models.filter((item) => item.remote);
  const selectedOllamaModel = ollama.models.find((item) => item.model === model || item.name === model);
  const ollamaCloudFallback = credentialsHydrated ? projectAi.getOllamaCloudFallbackProfile() : undefined;
  const automaticSuggestionsEnabled = aiWritingSettings.automaticSuggestions !== 'off';
  const automaticBusy = ['waiting', 'checking', 'generating'].includes(automaticWatcherStatus.phase) || activeRequests > 0;
  const automaticError = automaticWatcherStatus.phase === 'error' || automaticWatcherStatus.phase === 'blocked';
  const explicitWatcherActivity = !automaticSuggestionsEnabled
    && ['waiting', 'checking', 'generating', 'suggested', 'no-change', 'blocked', 'error'].includes(automaticWatcherStatus.phase);
  const aiControlTitle = automaticBusy
    ? `AI Settings · ${automaticWatcherStatus.reason ?? 'AI response in progress'}`
    : automaticSuggestionsEnabled
      ? `AI Settings · ${automaticWatcherStatus.reason ?? 'Automatic suggestions enabled'}`
      : 'AI Settings';
  const automaticStatus = automaticSuggestionsEnabled ? 'enabled' : 'off';

  const save = async () => {
    try {
      await projectAi.waitForCloudCredentialHydration();
      const selectedProvider: ProjectAiProviderMode = provider === 'disabled' ? 'local' : provider;
      if (selectedProvider === 'ollama') {
        const discovered = ollama.models.find((item) => item.model === model || item.name === model);
        if (discovered?.remote && privacyMode) throw new Error('Privacy Mode blocks Ollama Cloud. Choose a locally installed Ollama model or turn Privacy Mode off.');
      }
      projectAi.configure({
        enabled: true,
        provider: selectedProvider,
        localModelId,
        model,
        baseUrl: selectedProvider === 'ollama' ? DEFAULT_OLLAMA_BASE_URL : baseUrl,
        apiKey,
        rememberApiKey: false,
        privacyMode,
        autoExplainErrors: aiSettings.autoExplainErrors,
      });
      await projectAi.flushCloudCredentialPersistence();
      projectAiWriting.refreshAutomaticStatus();
      toast.success('AI settings updated.');
      notifyAiSettingsSaved();
      setOpen(false);
    } catch (reason) {
      toast.error(reason instanceof Error ? reason.message : 'Unable to save AI settings.');
    }
  };

  return (
    <>
      {mode !== 'dialog' ? <Button
        aria-label={aiControlTitle}
        className={`desktop-titlebar-no-drag h-7 px-2 sm:px-2.5 ${automaticSuggestionsEnabled ? 'bg-amber-50/70 text-amber-800 hover:bg-amber-100/80 hover:text-amber-900 dark:bg-amber-950/20 dark:text-amber-300 dark:hover:bg-amber-950/35' : ''}`}
        data-automatic-ai={automaticStatus}
        data-automatic-ai-phase={automaticWatcherStatus.phase}
        size="sm"
        title={aiControlTitle}
        variant="ghost"
        onClick={openAiSettingsDialog}
      >
        {automaticBusy ? (
          <LoaderCircle className="size-4 animate-spin sm:mr-1" aria-hidden="true" />
        ) : automaticError && (automaticSuggestionsEnabled || explicitWatcherActivity) ? (
          <CircleAlert className="size-4 sm:mr-1" aria-hidden="true" />
        ) : automaticSuggestionsEnabled ? (
          <Sparkles className="size-4 sm:mr-1" aria-hidden="true" />
        ) : (
          <Settings2 className="size-4 sm:mr-1" aria-hidden="true" />
        )}
        <span className="hidden sm:inline">AI Settings</span>
      </Button> : null}
      {mode !== 'trigger' ? <Dialog open={open} onOpenChange={(nextOpen) => { setOpen(nextOpen); if (!nextOpen) notifyAiSettingsClosed(); }}>
      <DialogContent className={AI_LARGE_DIALOG_CONTENT_CLASS} data-testid="topbar-ai-settings-dialog">
        <DialogHeader>
          <DialogTitle className="flex items-center gap-2"><Sparkles className="size-4" />AI settings</DialogTitle>
          <DialogDescription>AI settings are stored only in this browser’s IndexedDB.</DialogDescription>
        </DialogHeader>

        <section className="mb-4 rounded-lg border bg-muted/20 p-4" data-testid="automatic-ai-watcher-settings">
          <div className="flex items-center justify-between gap-8">
            <div className="min-w-0">
              <div className="font-medium">Automatic AI suggestions</div>
              <p className="mt-2 text-xs text-muted-foreground" data-testid="automatic-ai-watcher-status">
                <span className="font-medium text-foreground">{automaticWatcherStatus.phase}</span>
                {automaticWatcherStatus.provider ? ` · ${automaticWatcherStatus.provider}` : ''}
                {automaticWatcherStatus.model ? ` · ${automaticWatcherStatus.model}` : ''}
                {automaticWatcherStatus.reason ? <span className="ml-1">· {automaticWatcherStatus.reason}</span> : null}
              </p>
            </div>
            <button
              type="button"
              role="switch"
              aria-checked={automaticSuggestionsEnabled}
              aria-label="Automatic AI suggestions"
              data-testid="automatic-ai-suggest-toggle"
              className={`relative h-6 w-11 shrink-0 rounded-full border transition-colors ${automaticSuggestionsEnabled ? 'border-primary bg-primary' : 'border-border bg-muted'}`}
              onClick={() => projectAiWriting.configureAutomaticSuggestions(automaticSuggestionsEnabled ? 'off' : 'provider')}
            >
              <span className={`absolute left-0 top-0.5 size-5 rounded-full bg-background shadow-sm transition-transform ${automaticSuggestionsEnabled ? 'translate-x-5' : 'translate-x-0.5'}`} />
            </button>
          </div>
        </section>

        <div className="grid gap-4 lg:grid-cols-2">
          <section className="space-y-3 rounded-lg border p-4">
            <div className="flex items-start gap-2">
              <HardDrive className="mt-0.5 size-4" />
              <div>
                <div className="flex items-center gap-2"><span className="font-medium">Local AI</span><span className="rounded-full border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">Beta</span></div>
                <div className="text-xs text-muted-foreground">Runs in your browser. The larger model is recommended for Paper/Deep Review when your device has enough memory.</div>
              </div>
            </div>
            <div className="space-y-1">
              <Label htmlFor="topbar-ai-local-model">Local model</Label>
              <select
                id="topbar-ai-local-model"
                className="h-9 w-full rounded-md border bg-background px-2"
                value={localModelId}
                onChange={(event) => setLocalModelId(event.currentTarget.value as LocalAiModelId)}
              >
                {LOCAL_AI_MODELS.map((item) => (
                  <option key={item.id} value={item.id}>{item.label}{item.id === 'gemma-4-e4b-it-web' ? ' · Paper Review' : ''}</option>
                ))}
              </select>
              <p className="text-[11px] leading-4 text-muted-foreground">{selectedLocalModel?.description}</p>
            </div>
            <div className="rounded bg-muted/40 p-2 text-xs text-muted-foreground">
              Status: {localState?.status ?? 'not-installed'} · cached {formatBytes(localState?.installedBytes)}
              {progress !== undefined ? ` · ${progress}%` : ''}
            </div>
            <div className="flex flex-wrap gap-2">
              {!localInstalled && !downloading && (
                <Button
                  size="sm"
                  disabled={!localAi.capabilities?.supported}
                  onClick={() => void projectAi.downloadLocalModel(localModelId)
                    .then(() => projectAiWriting.refreshAutomaticStatus())
                    .catch((reason) => toast.error(reason instanceof Error ? reason.message : 'Model download failed.'))}
                >
                  <Download className="mr-1 size-4" />Download local model
                </Button>
              )}
              {downloading && <Button size="sm" variant="outline" onClick={() => projectAi.cancelLocalModelDownload()}>Cancel download</Button>}
              {localInstalled && (
                <Button
                  size="sm"
                  variant="outline"
                  onClick={() => void projectAi.selectLocalModel(localModelId)
                    .then(() => projectAi.reloadLocalModel())
                    .then(() => projectAiWriting.refreshAutomaticStatus())
                    .then(() => toast.success('Local AI loaded.'))
                    .catch((reason) => toast.error(reason instanceof Error ? reason.message : 'Unable to load Local AI.'))}
                >
                  Load model
                </Button>
              )}
            </div>
          </section>

          <section className="space-y-3 rounded-lg border p-4">
            <div className="flex items-start gap-2">
              <Settings2 className="mt-0.5 size-4" />
              <div>
                <div className="font-medium">AI Setup</div>
                <div className="text-xs text-muted-foreground">Choose browser Local AI, Ollama (local or cloud-backed), or BYOK AI with your own cloud API key.</div>
              </div>
            </div>
            <div className="space-y-1">
              <Label htmlFor="topbar-ai-provider">Provider</Label>
              <select
                id="topbar-ai-provider"
                className="h-9 w-full rounded-md border bg-background px-2"
                value={provider}
                onChange={(event) => {
                  const next = event.currentTarget.value as ProjectAiProviderMode;
                  setProvider(next);
                  setApiKey(projectAi.getApiKeyForProvider(next, baseUrl));
                  if (next === 'ollama') {
                    // Selecting Ollama is the explicit user action that permits
                    // Learnitive to probe localhost and may trigger the browser's
                    // Local Network Access permission prompt.
                    setBaseUrl(DEFAULT_OLLAMA_BASE_URL);
                    setApiKey('');
                    setModel(aiSettings.provider === 'ollama' && aiSettings.model ? aiSettings.model : (localOllamaModels[0]?.model ?? (!privacyMode ? ollamaCloudModels[0]?.model ?? '' : '')));
                    void projectAi.refreshOllamaState(DEFAULT_OLLAMA_BASE_URL).then((state) => {
                      const firstAllowed = state.models.find((item) => !item.remote || !privacyMode);
                      if (firstAllowed?.model) setModel((current) => current || firstAllowed.model);
                    }).catch(() => undefined);
                  } else if (isCloudProvider(next)) {
                    const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/u, '');
                    const profile = projectAi.getEphemeralCloudProfiles().find((item) => item.provider === next && (next !== 'openai-compatible' || (item.baseUrl ?? '') === normalizedBaseUrl));
                    setModel(profile?.model || defaultProjectAiModel(next));
                  }
                }}
              >
                <option value="auto">Auto · local first, cloud for Agent/vision</option>
                <option value="local">Local AI · browser</option>
                <option value="ollama">Ollama · Local + Cloud</option>
                {CLOUD_PROVIDERS.map((item) => <option key={item.id} value={item.id}>{item.label}</option>)}
              </select>
            </div>

            {provider === 'auto' && (
              <div className="rounded-md border bg-muted/20 p-3 text-xs text-muted-foreground">
                <strong className="text-foreground">Auto routing</strong> · Learnitive tries browser-local AI for ordinary text work. Agent or vision requests prefer your most recently configured BYOK cloud profile when Privacy Mode permits cloud context. Choosing Auto is the explicit permission for this routing; Local and Ollama modes never silently fall through to cloud.
              </div>
            )}

            {provider === 'ollama' && (
              <div className="space-y-3 rounded-md border bg-muted/20 p-3" data-testid="topbar-ai-ollama-settings">
                <div className="flex items-start gap-2">
                  <Server className="mt-0.5 size-4" aria-hidden="true" />
                  <div>
                    <div className="font-medium">Ollama</div>
                    <p className="text-xs text-muted-foreground">Connect to Ollama running on your computer. Learnitive checks <code>localhost:11434</code> only after you select Ollama, then distinguishes locally installed models from Ollama Cloud-backed models. Your browser may ask for Local Network Access at that point.</p>
                  </div>
                </div>
                <div className="flex flex-wrap items-center gap-2">
                  <Button
                    type="button"
                    size="sm"
                    variant="outline"
                    disabled={ollama.status === 'checking'}
                    onClick={() => void projectAi.refreshOllamaState(DEFAULT_OLLAMA_BASE_URL).then((state) => {
                      if (state.status === 'connected') {
                        if (!model) {
                          const firstAllowed = state.models.find((item) => !item.remote || !privacyMode);
                          if (firstAllowed?.model) setModel(firstAllowed.model);
                        }
                        toast.success(`Connected to Ollama${state.version ? ` ${state.version}` : ''}.`);
                      } else if (state.error) toast.error(state.error);
                    })}
                  >
                    {ollama.status === 'checking' ? <LoaderCircle className="mr-1 size-4 animate-spin" /> : <RefreshCw className="mr-1 size-4" />}
                    Refresh Ollama
                  </Button>
                  <span className="text-xs text-muted-foreground" data-testid="topbar-ai-ollama-status">
                    {ollama.status === 'connected'
                      ? `Connected${ollama.version ? ` · ${ollama.version}` : ''} · ${ollama.models.length} model${ollama.models.length === 1 ? '' : 's'}`
                      : ollama.status === 'checking'
                        ? 'Checking…'
                        : ollama.error ?? 'Not checked'}
                  </span>
                </div>
                <div className="space-y-1">
                  <Label htmlFor="topbar-ai-ollama-model">Ollama model</Label>
                  {ollama.models.length > 0 ? (
                    <select id="topbar-ai-ollama-model" className="h-9 w-full rounded-md border bg-background px-2" value={model} onChange={(event) => setModel(event.currentTarget.value)}>
                      <option value="">Select an Ollama model</option>
                      {ollama.models.map((item) => (
                        <option key={item.model} value={item.model} disabled={item.remote && privacyMode}>
                          {item.name}{item.parameterSize ? ` · ${item.parameterSize}` : ''}{item.quantizationLevel ? ` · ${item.quantizationLevel}` : ''}{item.remote ? ` · Ollama Cloud${privacyMode ? ' · blocked by Privacy Mode' : ''}` : ' · Local'}
                        </option>
                      ))}
                    </select>
                  ) : (
                    <Input id="topbar-ai-ollama-model" value={model} onChange={(event) => setModel(event.currentTarget.value)} placeholder="e.g. qwen3:8b" />
                  )}
                  <p className="text-[11px] leading-4 text-muted-foreground">Models are discovered after you select Ollama or click Refresh Ollama. Learnitive does not probe localhost while another AI provider is selected. Local models keep inference on your machine. Ollama Cloud-backed models are available when Privacy Mode is off and may send manuscript context to Ollama Cloud.</p>
                </div>
                <a className="inline-flex items-center gap-1 text-xs font-medium underline underline-offset-4" href="/docs/ollama">Ollama setup guide <ExternalLink className="size-3" aria-hidden="true" /></a>
                <p className="rounded border border-emerald-500/20 bg-emerald-500/5 p-2 text-xs text-muted-foreground"><strong className="text-foreground">{selectedOllamaModel?.remote ? 'Ollama Cloud:' : 'Local Ollama:'}</strong> {selectedOllamaModel?.remote ? 'This model is cloud-backed through your local Ollama service. Privacy Mode must be off and manuscript context may leave this computer through Ollama Cloud.' : 'Learnitive verifies that the selected model is installed locally before sending manuscript context.'} If Ollama stops, Learnitive reports the local failure. Retry Ollama or switch models/providers explicitly; it never sends the request to BYOK AI automatically.</p>
              </div>
            )}

            {isCloudProvider(provider) && (
              <>
                <div className="rounded-md border bg-muted/20 p-3 text-xs text-muted-foreground"><strong className="text-foreground">BYOK AI</strong> · Use your own API key with a supported cloud provider. Credentials stay in this browser’s IndexedDB.</div>
                <AiProviderModelSelect provider={provider} model={model} onModelChange={setModel} idPrefix="topbar-ai" />
                <div className="space-y-1">
                  <Label htmlFor="topbar-ai-key">API key</Label>
                  <div className="flex gap-1">
                    <Input id="topbar-ai-key" type="password" autoComplete="off" value={apiKey} onChange={(event) => setApiKey(event.currentTarget.value)} />
                    <Button type="button" size="icon" variant="outline" aria-label="Clear API key" onClick={() => { setApiKey(''); projectAi.clearApiKey(provider, baseUrl); }}>
                      <KeyRound className="size-4" />
                    </Button>
                  </div>
                </div>
                {provider === 'openai-compatible' && (
                  <div className="space-y-1">
                    <Label htmlFor="topbar-ai-base-url">Base URL</Label>
                    <Input
                      id="topbar-ai-base-url"
                      value={baseUrl}
                      placeholder="https://…/v1"
                      onChange={(event) => {
                        const value = event.currentTarget.value;
                        setBaseUrl(value);
                        setApiKey(projectAi.getApiKeyForProvider(provider, value));
                      }}
                    />
                  </div>
                )}
                <label className="flex items-start gap-2 rounded border p-2 text-xs">
                  <input className="mt-0.5" type="checkbox" checked={privacyMode} onChange={(event) => setPrivacyMode(event.currentTarget.checked)} />
                  <span><strong>Privacy Mode</strong><span className="mt-0.5 block text-muted-foreground">Off by default. Turn on to block manuscript/project context from cloud providers.</span></span>
                </label>
              </>
            )}
          </section>
        </div>

        <div className="flex items-center justify-between gap-2">
          <Button
            variant="ghost"
            onClick={() => {
              projectAi.clearLocalAiData();
              projectAiWriting.refreshAutomaticStatus();
              toast.success('AI data cleared from this browser.');
            }}
          >
            Clear AI data
          </Button>
          <div className="flex gap-2">
            <Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
            <Button disabled={isCloudProvider(provider) && !credentialsHydrated} onClick={() => void save()}>Save AI Setup</Button>
          </div>
        </div>
      </DialogContent>
    </Dialog> : null}
    </>
  );
}
