'use client';

import { useState } from 'react';
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { AlertTriangle, CheckCircle2, Loader2, ShieldCheck, X } from 'lucide-react';

import { installBuilderDraft, patchBuilderSpec } from '../../../../_lib/api';
import { cn } from '../../../../_lib/cn';
import type {
  Draft,
  InstallFailureReason,
  InstallResponse,
} from '../../../../_lib/builderTypes';

/**
 * Frontend mirror of middleware's `bumpPatchVersion` (Theme C). Strips
 * any prerelease tag and increments the patch segment. Returns input
 * unchanged for non-semver garbage so the operator's typo stays
 * surfaced rather than silently rewritten.
 *
 * Examples: 0.1.0 → 0.1.1, 1.2.3-alpha.4 → 1.2.4, 'whatever' → 'whatever'.
 */
function bumpPatchVersion(version: string): string {
  const m = /^(\d+)\.(\d+)\.(\d+)(?:-[A-Za-z0-9.-]+)?$/.exec(version);
  if (!m) return version;
  const [, major, minor, patch] = m;
  return `${major}.${minor}.${String(Number(patch) + 1)}`;
}

/**
 * Install-Diff-Gate (B.6-2 / M3 from the master plan).
 *
 * Shows the operator the full surface that will be committed to the plugin
 * registry, then POSTs `/api/v1/builder/drafts/:id/install` and reacts to
 * the orchestrator's discriminated `InstallResponse`:
 *
 *   reason: undefined (ok=true)  → flash success then push /store?highlight=…
 *   reason: 'conflict'          → inline banner ("ID schon installiert" /
 *                                  "Version bereits hochgeladen") with hint;
 *                                  user has to change spec and rebuild
 *   reason: 'build_failed' /     → banner with the structured tsc errors / Zod
 *           'codegen_failed' /    issues — operator goes back to the Editor pane
 *           'spec_invalid'
 *   reason: 'manifest_invalid' / → generic banner; rare in B.6 because we
 *           'too_large'           always build from a fresh codegen
 *   reason: 'pipeline_failed' /  → banner with raw message; treat as 5xx
 *           'ingest_failed'
 *
 * The modal does NOT pre-check for conflicts client-side: the server is the
 * single source of truth for the registry state, and the round-trip is
 * already required for the actual commit. Reserved-prefix/peer-dep checks
 * are server-side too (B.7+).
 */

type InstallT = ReturnType<typeof useTranslations<'builder.install'>>;

export interface InstallDiffModalProps {
  draft: Draft;
  open: boolean;
  onClose: () => void;
  /** Optional override for tests — defaults to the real installBuilderDraft. */
  install?: (draftId: string) => Promise<InstallResponse>;
}

type Phase =
  | { kind: 'idle' }
  | { kind: 'installing' }
  | { kind: 'failed'; failure: InstallFailureBody }
  | { kind: 'succeeded'; publishedAgentId: string; version: string };

interface InstallFailureBody {
  reason: InstallFailureReason;
  code: string;
  message: string;
  details?: unknown;
}

export function InstallDiffModal({
  draft,
  open,
  onClose,
  install,
}: InstallDiffModalProps): React.ReactElement | null {
  const t = useTranslations('builder.install');
  const router = useRouter();
  const [phase, setPhase] = useState<Phase>({ kind: 'idle' });

  if (!open) return null;

  const busy = phase.kind === 'installing';

  const handleInstall = async (): Promise<void> => {
    setPhase({ kind: 'installing' });
    try {
      const fn = install ?? installBuilderDraft;
      const result = await fn(draft.id);
      if (result.ok) {
        setPhase({
          kind: 'succeeded',
          publishedAgentId: result.publishedAgentId,
          version: result.version,
        });
        // Brief delay so the user sees the success banner before the redirect.
        window.setTimeout(() => {
          router.push(
            `/store?highlight=${encodeURIComponent(result.publishedAgentId)}`,
          );
        }, 600);
        return;
      }
      setPhase({
        kind: 'failed',
        failure: {
          reason: result.reason,
          code: result.code,
          message: result.message,
          ...(result.details !== undefined ? { details: result.details } : {}),
        },
      });
    } catch (err) {
      setPhase({
        kind: 'failed',
        failure: {
          reason: 'ingest_failed',
          code: 'builder.transport_error',
          message: err instanceof Error ? err.message : String(err),
        },
      });
    }
  };

  const handleClose = (): void => {
    if (busy) return;
    setPhase({ kind: 'idle' });
    onClose();
  };

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center px-4 py-6"
      role="dialog"
      aria-modal="true"
      aria-label={t('publishTitle')}
    >
      <button
        type="button"
        onClick={handleClose}
        aria-label={t('closeModalAria')}
        disabled={busy}
        className="absolute inset-0 bg-[color:var(--ink)]/40 backdrop-blur-sm transition disabled:cursor-wait"
      />

      <div
        className={cn(
          'relative z-10 flex w-full max-w-4xl flex-col',
          'border border-[color:var(--rule-strong)] bg-[color:var(--paper)]',
          'shadow-[0_30px_80px_-20px_rgba(0,0,0,0.4)]',
          'max-h-[90vh] overflow-hidden',
        )}
      >
        <header className="flex items-start justify-between gap-6 border-b border-[color:var(--rule)] px-7 py-5">
          <div className="min-w-0">
            <div className="flex items-center gap-2 text-[11px] uppercase tracking-[0.22em] text-[color:var(--accent)]">
              <ShieldCheck className="size-3.5" aria-hidden />
              {t('diffGateEyebrow')}
            </div>
            <h2 className="font-display mt-1 text-2xl font-medium leading-tight text-[color:var(--ink)]">
              {t('publishTitle')}
            </h2>
            <p className="mt-2 text-[12px] leading-relaxed text-[color:var(--muted-ink)]">
              {t('publishLeadPrefix')}{' '}
              <span className="font-mono-num">{draft.spec.id}</span> v
              {draft.spec.version} {t('publishLeadSuffix')}
            </p>
          </div>
          <button
            type="button"
            onClick={handleClose}
            disabled={busy}
            className="text-[color:var(--muted-ink)] transition hover:text-[color:var(--ink)] disabled:opacity-40"
            aria-label={t('closeAria')}
          >
            <X className="size-4" aria-hidden />
          </button>
        </header>

        <div className="flex-1 overflow-y-auto px-7 py-6">
          {phase.kind === 'failed' ? (
            <FailureBanner
              t={t}
              failure={phase.failure}
              onRetry={() => setPhase({ kind: 'idle' })}
              currentVersion={draft.spec.version}
              onBumpAndRetry={async () => {
                // Theme C, retry-path: server-side auto-bump only
                // triggers at clone-from-installed. Drafts that predate
                // that fix (or where the operator manually re-edited
                // the version back) still hit duplicate_version. This
                // handler patches the spec.version inline and immediately
                // retries the install — zero-friction self-fix without
                // forcing the operator to navigate to the spec editor.
                const next = bumpPatchVersion(draft.spec.version);
                if (next === draft.spec.version) {
                  setPhase({
                    kind: 'failed',
                    failure: {
                      reason: 'conflict',
                      code: 'builder.bump_invalid_semver',
                      message: t('bumpInvalidSemver', {
                        version: draft.spec.version,
                      }),
                    },
                  });
                  return;
                }
                try {
                  await patchBuilderSpec(draft.id, [
                    { op: 'replace', path: '/version', value: next },
                  ]);
                } catch (err) {
                  setPhase({
                    kind: 'failed',
                    failure: {
                      reason: 'conflict',
                      code: 'builder.bump_patch_failed',
                      message:
                        err instanceof Error ? err.message : String(err),
                    },
                  });
                  return;
                }
                // Server now has the bumped spec; re-trigger install.
                await handleInstall();
              }}
            />
          ) : null}
          {phase.kind === 'succeeded' ? (
            <SuccessBanner
              t={t}
              publishedAgentId={phase.publishedAgentId}
              version={phase.version}
            />
          ) : null}

          <DiffBody draft={draft} t={t} />
        </div>

        <footer className="flex items-center justify-end gap-3 border-t border-[color:var(--rule)] px-7 py-4">
          <button
            type="button"
            onClick={handleClose}
            disabled={busy}
            className={cn(
              'rounded-md px-4 py-2 text-[12px] font-semibold uppercase tracking-[0.18em]',
              'text-[color:var(--fg-muted)] transition-colors',
              'hover:bg-[color:var(--bg-soft)] hover:text-[color:var(--fg-strong)]',
              'disabled:opacity-40 disabled:cursor-not-allowed',
            )}
          >
            {t('back')}
          </button>
          <button
            type="button"
            onClick={() => void handleInstall()}
            disabled={busy || phase.kind === 'succeeded'}
            className={cn(
              'inline-flex items-center gap-2 rounded-md px-5 py-2 text-[12px] font-semibold uppercase tracking-[0.18em]',
              'bg-[color:var(--accent)] text-white shadow-[var(--shadow-cta)]',
              'transition-opacity',
              'disabled:opacity-50 disabled:cursor-not-allowed',
            )}
          >
            {busy ? (
              <>
                <Loader2 className="size-3.5 animate-spin" aria-hidden />
                {t('publishing')}
              </>
            ) : phase.kind === 'succeeded' ? (
              <>
                <CheckCircle2 className="size-3.5" aria-hidden />
                {t('succeeded')}
              </>
            ) : (
              t('publish')
            )}
          </button>
        </footer>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Body sections
// ---------------------------------------------------------------------------

function DiffBody({ draft, t }: { draft: Draft; t: InstallT }): React.ReactElement {
  const spec = draft.spec;
  const skillPromptSlot = draft.slots['skill-prompt'];
  // Skeleton arrays may be undefined on drafts that were persisted before
  // the LLM filled every section (patch_spec can omit untouched fields).
  // The rest of the builder UI follows the same `?? []` convention, see
  // Workspace.tsx / SpecEditor.tsx / SpecOverview.tsx / manifestLinter.ts.
  const tools = spec.tools ?? [];
  const dependsOn = spec.depends_on ?? [];
  const outbound = spec.network?.outbound ?? [];
  const setupFields = spec.setup_fields ?? [];
  return (
    <div className="space-y-6">
      <Section title={t('sectionIdentity')}>
        <KV k={t('kvId')} v={<span className="font-mono-num">{spec.id}</span>} />
        <KV k={t('kvName')} v={spec.name} />
        <KV k={t('kvVersion')} v={<span className="font-mono-num">{spec.version}</span>} />
        <KV k={t('kvCategory')} v={spec.category} />
        <KV
          k={t('kvDomain')}
          v={<span className="font-mono-num">{spec.domain || '—'}</span>}
        />
        <KV k={t('kvTemplate')} v={spec.template ?? 'agent-integration'} />
        <KV k={t('kvDescription')} v={spec.description} />
      </Section>

      <Section title={t('sectionSkill')}>
        <KV k={t('kvRole')} v={spec.skill.role} />
        {spec.skill.tonality ? (
          <KV k={t('kvTonality')} v={spec.skill.tonality} />
        ) : null}
        {skillPromptSlot ? (
          <details className="mt-2 rounded-md border border-[color:var(--divider)] bg-[color:var(--bg-soft)]">
            <summary className="cursor-pointer px-3 py-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-[color:var(--fg-muted)]">
              {t('skillPromptSlotSummary', { count: skillPromptSlot.length })}
            </summary>
            <pre className="max-h-72 overflow-auto whitespace-pre-wrap break-words border-t border-[color:var(--divider)] px-3 py-2 text-[11px] font-mono leading-relaxed text-[color:var(--fg-strong)]">
              {skillPromptSlot}
            </pre>
          </details>
        ) : (
          <p className="text-[11px] italic text-[color:var(--fg-subtle)]">
            {t('noSkillPromptSlot')}
          </p>
        )}
      </Section>

      <Section
        title={t('sectionTools')}
        subtitle={t('toolsCount', { count: tools.length })}
      >
        {tools.length === 0 ? (
          <EmptyHint>{t('noToolsDefined')}</EmptyHint>
        ) : (
          <ul className="space-y-2">
            {tools.map((t) => (
              <li
                key={t.id}
                className="rounded-md border border-[color:var(--divider)] bg-[color:var(--bg-soft)] px-3 py-2"
              >
                <div className="flex items-baseline justify-between gap-3">
                  <span className="font-mono-num text-[12px] font-semibold text-[color:var(--fg-strong)]">
                    {t.id}
                  </span>
                </div>
                <p className="mt-1 text-[11px] text-[color:var(--fg-muted)]">
                  {t.description}
                </p>
                {t.input ? (
                  <details className="mt-2">
                    <summary className="cursor-pointer text-[10px] font-semibold uppercase tracking-[0.16em] text-[color:var(--fg-subtle)]">
                      input schema
                    </summary>
                    <pre className="mt-1 max-h-48 overflow-auto whitespace-pre-wrap break-words rounded border border-[color:var(--divider)] bg-[color:var(--paper)] px-2 py-1.5 text-[10px] font-mono leading-relaxed text-[color:var(--fg-strong)]">
                      {JSON.stringify(t.input, null, 2)}
                    </pre>
                  </details>
                ) : null}
              </li>
            ))}
          </ul>
        )}
      </Section>

      <Section
        title={t('sectionDependencies')}
        subtitle={t('sectionDependenciesSubtitle')}
      >
        {dependsOn.length === 0 ? (
          <EmptyHint>{t('noDependencies')}</EmptyHint>
        ) : (
          <ul className="space-y-1">
            {dependsOn.map((d) => (
              <li
                key={d}
                className="font-mono-num text-[12px] text-[color:var(--fg-strong)]"
              >
                {d}
              </li>
            ))}
          </ul>
        )}
      </Section>

      <Section
        title={t('sectionNetwork')}
        subtitle="permissions.network.outbound"
      >
        {outbound.length === 0 ? (
          <EmptyHint>{t('noOutboundHosts')}</EmptyHint>
        ) : (
          <ul className="flex flex-wrap gap-1.5">
            {outbound.map((h) => (
              <li
                key={h}
                className="font-mono-num rounded-full border border-[color:var(--divider)] bg-[color:var(--bg-soft)] px-2.5 py-0.5 text-[11px] text-[color:var(--fg-strong)]"
              >
                {h}
              </li>
            ))}
          </ul>
        )}
      </Section>

      <Section
        title={t('sectionSetupFields')}
        subtitle={t('sectionSetupFieldsSubtitle')}
      >
        {setupFields.length === 0 ? (
          <EmptyHint>{t('noSetupNeeded')}</EmptyHint>
        ) : (
          <ul className="space-y-1">
            {setupFields.map((f) => (
              <li
                key={f.key}
                className="flex items-baseline justify-between gap-3 rounded-md border border-[color:var(--divider)] px-3 py-1.5"
              >
                <span className="font-mono-num text-[12px] text-[color:var(--fg-strong)]">
                  {f.key}
                </span>
                <span className="text-[10px] uppercase tracking-[0.18em] text-[color:var(--fg-subtle)]">
                  {f.type ?? 'string'}
                  {f.required ? ` · ${t('required')}` : ''}
                </span>
              </li>
            ))}
          </ul>
        )}
      </Section>
    </div>
  );
}

function Section({
  title,
  subtitle,
  children,
}: {
  title: string;
  subtitle?: string;
  children: React.ReactNode;
}): React.ReactElement {
  return (
    <section>
      <div className="mb-2 flex items-baseline gap-2">
        <h3 className="font-display text-[14px] font-semibold text-[color:var(--fg-strong)]">
          {title}
        </h3>
        {subtitle ? (
          <span className="text-[11px] text-[color:var(--fg-subtle)]">{subtitle}</span>
        ) : null}
      </div>
      <div className="space-y-1.5 text-[12px]">{children}</div>
    </section>
  );
}

function KV({
  k,
  v,
}: {
  k: string;
  v: React.ReactNode;
}): React.ReactElement {
  return (
    <div className="grid grid-cols-[140px_1fr] items-baseline gap-3">
      <dt className="text-[10px] font-semibold uppercase tracking-[0.18em] text-[color:var(--fg-subtle)]">
        {k}
      </dt>
      <dd className="text-[12px] text-[color:var(--fg-strong)]">{v}</dd>
    </div>
  );
}

function EmptyHint({ children }: { children: React.ReactNode }): React.ReactElement {
  return (
    <p className="text-[11px] italic text-[color:var(--fg-subtle)]">{children}</p>
  );
}

// ---------------------------------------------------------------------------
// Banners
// ---------------------------------------------------------------------------

function FailureBanner({
  t,
  failure,
  onRetry,
  currentVersion,
  onBumpAndRetry,
}: {
  t: InstallT;
  failure: InstallFailureBody;
  onRetry: () => void;
  currentVersion: string;
  onBumpAndRetry: () => void | Promise<void>;
}): React.ReactElement {
  const headingKey = HEADING_KEYS[failure.reason];
  const heading = headingKey ? t(headingKey) : t('failureGenericHeading');
  const hintKey = HINT_KEYS[failure.reason];
  const hint = hintKey ? t(hintKey) : undefined;
  // Theme C: only the duplicate_version sub-case of `conflict` is fixable
  // by bumping. We can't reliably distinguish from `id` collisions on
  // reason alone, so we ALSO check the code prefix. `package.duplicate_version`
  // is the canonical code emitted by packageUploadService.
  const isDuplicateVersion =
    failure.reason === 'conflict' &&
    failure.code.includes('duplicate_version');
  const bumped = isDuplicateVersion ? bumpPatchVersion(currentVersion) : null;
  const canBump = bumped !== null && bumped !== currentVersion;

  return (
    <div className="mb-6 rounded-md border border-[color:var(--danger)]/40 bg-[color:var(--danger)]/6 px-4 py-3">
      <div className="flex items-start gap-3">
        <AlertTriangle
          className="mt-0.5 size-4 shrink-0 text-[color:var(--danger)]"
          aria-hidden
        />
        <div className="min-w-0 flex-1">
          <div className="flex items-baseline justify-between gap-3">
            <p className="font-display text-[13px] font-semibold text-[color:var(--danger)]">
              {heading}
            </p>
            <span className="font-mono-num text-[10px] uppercase tracking-[0.16em] text-[color:var(--fg-subtle)]">
              {failure.code}
            </span>
          </div>
          <p className="mt-1 text-[12px] text-[color:var(--fg-strong)]">
            {failure.message}
          </p>
          {hint ? (
            <p className="mt-2 text-[11px] italic text-[color:var(--fg-muted)]">
              {hint}
            </p>
          ) : null}
          <FailureDetails failure={failure} t={t} />
          <div className="mt-3 flex flex-wrap items-center gap-2">
            {canBump ? (
              <button
                type="button"
                onClick={() => void onBumpAndRetry()}
                className="inline-flex items-center gap-2 rounded-md bg-[color:var(--accent)] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm transition-opacity hover:opacity-90"
              >
                {t('bumpAndPublish', { current: currentVersion, next: bumped ?? '' })}
              </button>
            ) : null}
            <button
              type="button"
              onClick={onRetry}
              className="inline-flex items-center gap-2 rounded-md border border-[color:var(--divider)] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--fg-strong)] transition-colors hover:bg-[color:var(--bg-soft)]"
            >
              {t('retry')}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function FailureDetails({
  failure,
  t,
}: {
  failure: InstallFailureBody;
  t: InstallT;
}): React.ReactElement | null {
  const details = failure.details;
  if (!details || typeof details !== 'object') return null;

  // build_failed → { errors: BuildError[], stderrTail, ... }
  if (failure.reason === 'build_failed') {
    const errors = (details as { errors?: unknown[] }).errors;
    if (Array.isArray(errors) && errors.length > 0) {
      return (
        <details className="mt-2">
          <summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--fg-muted)]">
            {t('typescriptErrors', { count: errors.length })}
          </summary>
          <ul className="mt-1.5 space-y-1">
            {errors.slice(0, 10).map((e, i) => {
              const row = e as {
                path?: string;
                line?: number;
                col?: number;
                code?: string;
                message?: string;
              };
              return (
                <li
                  key={i}
                  className="rounded border border-[color:var(--divider)] bg-[color:var(--paper)] px-2 py-1.5 text-[10px] font-mono leading-relaxed"
                >
                  <span className="text-[color:var(--fg-subtle)]">
                    {row.path ?? '?'}:{row.line ?? '?'}:{row.col ?? '?'}
                  </span>
                  <span className="ml-2 text-[color:var(--danger)]">
                    {row.code ?? ''}
                  </span>
                  <div className="mt-0.5 text-[color:var(--fg-strong)]">
                    {row.message ?? ''}
                  </div>
                </li>
              );
            })}
          </ul>
        </details>
      );
    }
  }

  // codegen_failed → { issues: CodegenIssue[] }
  if (failure.reason === 'codegen_failed') {
    const issues = (details as { issues?: unknown[] }).issues;
    if (Array.isArray(issues) && issues.length > 0) {
      return (
        <details className="mt-2">
          <summary className="cursor-pointer text-[11px] font-semibold uppercase tracking-[0.16em] text-[color:var(--fg-muted)]">
            {t('codegenIssues', { count: issues.length })}
          </summary>
          <ul className="mt-1.5 space-y-1">
            {issues.map((i, idx) => {
              const row = i as { code?: string; detail?: string };
              return (
                <li
                  key={idx}
                  className="rounded border border-[color:var(--divider)] bg-[color:var(--paper)] px-2 py-1.5 text-[11px]"
                >
                  <span className="font-mono-num text-[10px] uppercase tracking-[0.14em] text-[color:var(--fg-subtle)]">
                    {row.code ?? '?'}
                  </span>
                  <div className="mt-0.5 text-[color:var(--fg-strong)]">
                    {row.detail ?? ''}
                  </div>
                </li>
              );
            })}
          </ul>
        </details>
      );
    }
  }

  return null;
}

function SuccessBanner({
  t,
  publishedAgentId,
  version,
}: {
  t: InstallT;
  publishedAgentId: string;
  version: string;
}): React.ReactElement {
  return (
    <div className="mb-6 rounded-md border border-[color:var(--success)]/40 bg-[color:var(--success)]/6 px-4 py-3">
      <div className="flex items-start gap-3">
        <CheckCircle2
          className="mt-0.5 size-4 shrink-0 text-[color:var(--success)]"
          aria-hidden
        />
        <div className="min-w-0 flex-1">
          <p className="font-display text-[13px] font-semibold text-[color:var(--success)]">
            {t('successHeading')}
          </p>
          <p className="mt-1 text-[12px] text-[color:var(--fg-strong)]">
            <span className="font-mono-num">{publishedAgentId}</span> v{version}{' '}
            {t('successBody')}
          </p>
        </div>
      </div>
    </div>
  );
}

const HEADING_KEYS: Partial<Record<InstallFailureReason, string>> = {
  conflict: 'headingConflict',
  build_failed: 'headingBuildFailed',
  codegen_failed: 'headingCodegenFailed',
  spec_invalid: 'headingSpecInvalid',
  manifest_invalid: 'headingManifestInvalid',
  too_large: 'headingTooLarge',
  pipeline_failed: 'headingPipelineFailed',
  ingest_failed: 'headingIngestFailed',
  draft_not_found: 'headingDraftNotFound',
};

const HINT_KEYS: Partial<Record<InstallFailureReason, string>> = {
  conflict: 'hintConflict',
  build_failed: 'hintBuildFailed',
  codegen_failed: 'hintCodegenFailed',
  spec_invalid: 'hintSpecInvalid',
  too_large: 'hintTooLarge',
};
