/**
 * Spec-to-PR — paste a plain-English feature spec, get back a draft PR
 * generated by the Claude API.
 *
 *   GET  /:owner/:repo/spec   — form (requires write access)
 *   POST /:owner/:repo/spec   — hands off to lib/spec-to-pr.ts, redirects to
 *                                the new PR on success, re-renders the form
 *                                with an error banner on failure
 *
 * The backend (`createSpecPR` in `src/lib/spec-to-pr.ts`) is being built in
 * parallel. We import it dynamically so this file compiles and its tests
 * pass even if the module is not yet on disk — if the import fails we
 * fall back to a "Backend not available" banner.
 *
 * 2026 polish: scoped `.specs-*` class system. Eyebrow + display headline +
 * subtitle hero, card sections for the form + the "how this works" steps,
 * primary CTA on the submit button. All form fields, names, actions, and
 * the dynamic-import behaviour are unchanged.
 */
import { Hono } from "hono";
import { and, eq } from "drizzle-orm";
import { db } from "../db";
import { repositories, users, issues } from "../db/schema";
import { Layout } from "../views/layout";
import { RepoHeader } from "../views/components";
import { softAuth, requireAuth } from "../middleware/auth";
import type { AuthEnv } from "../middleware/auth";
import { listBranches } from "../git/repository";

const specs = new Hono<AuthEnv>();

// Tiny inline script that disables the submit button + textarea while the
// request is in-flight so users don't accidentally double-click and trigger
// two 10-30s Claude calls. Rendered as a plain <script> tag.
const DISABLE_ON_SUBMIT_JS = `
(function() {
  var form = document.getElementById('spec-form');
  if (!form) return;
  form.addEventListener('submit', function() {
    var btn = form.querySelector('button[type="submit"]');
    var ta = form.querySelector('textarea[name="spec"]');
    if (btn) {
      btn.disabled = true;
      btn.textContent = 'Working… this can take 10-30s';
    }
    if (ta) ta.readOnly = true;
  });
})();
`;

// ─── Scoped CSS (.specs-*) ─────────────────────────────────────────────────
const specsStyles = `
  .specs-wrap { max-width: 1100px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-8); }

  /* ─── Header ─── */
  .specs-head { margin-bottom: var(--space-5); }
  .specs-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-transform: uppercase;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.16em;
    color: var(--text-muted);
    font-weight: 600;
    margin-bottom: 10px;
  }
  .specs-eyebrow-dot {
    width: 8px; height: 8px;
    border-radius: 9999px;
    background: linear-gradient(135deg, #8c6dff, #36c5d6);
    box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
  }
  .specs-pill-experimental {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    margin-left: 4px;
    border-radius: 9999px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    background: rgba(251,191,36,0.12);
    color: #fde68a;
    box-shadow: inset 0 0 0 1px rgba(251,191,36,0.32);
  }
  .specs-title {
    font-family: var(--font-display);
    font-size: clamp(26px, 3.6vw, 40px);
    font-weight: 800;
    letter-spacing: -0.028em;
    line-height: 1.05;
    margin: 0 0 6px;
    color: var(--text-strong);
  }
  .specs-title-grad {
    background-image: linear-gradient(135deg, #a48bff 0%, #8c6dff 50%, #36c5d6 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
  .specs-sub {
    margin: 0;
    font-size: 14px;
    color: var(--text-muted);
    line-height: 1.5;
    max-width: 720px;
  }
  .specs-sub a { color: var(--accent); text-decoration: none; }
  .specs-sub a:hover { text-decoration: underline; }
  .specs-sub code {
    font-family: var(--font-mono);
    font-size: 12px;
    background: rgba(255,255,255,0.04);
    padding: 1px 6px;
    border-radius: 4px;
  }

  /* ─── Banners ─── */
  .specs-banner {
    margin-bottom: var(--space-4);
    padding: 10px 14px;
    border-radius: 10px;
    font-size: 13.5px;
    border: 1px solid var(--border);
    background: rgba(255,255,255,0.025);
    color: var(--text);
    display: flex;
    align-items: flex-start;
    gap: 10px;
    line-height: 1.45;
  }
  .specs-banner.is-info {
    border-color: rgba(54,197,214,0.40);
    background: rgba(54,197,214,0.08);
    color: #cffafe;
  }
  .specs-banner.is-error {
    border-color: rgba(248,113,113,0.40);
    background: rgba(248,113,113,0.08);
    color: #fecaca;
  }
  .specs-banner-dot {
    width: 8px; height: 8px;
    border-radius: 9999px;
    background: currentColor;
    margin-top: 6px;
    flex-shrink: 0;
  }
  .specs-banner a {
    color: inherit;
    text-decoration: underline;
    font-weight: 600;
  }

  /* ─── Section card ─── */
  .specs-section {
    margin-bottom: var(--space-5);
    background: var(--bg-elevated);
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    position: relative;
  }
  .specs-section::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 2px;
    background: linear-gradient(90deg, transparent 0%, #8c6dff 30%, #36c5d6 70%, transparent 100%);
    opacity: 0.55;
    pointer-events: none;
  }
  .specs-section-head {
    padding: var(--space-4) var(--space-5) var(--space-3);
    border-bottom: 1px solid var(--border);
  }
  .specs-section-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: 16px;
    font-weight: 700;
    letter-spacing: -0.018em;
    color: var(--text-strong);
  }
  .specs-section-sub {
    margin: 6px 0 0;
    font-size: 12.5px;
    color: var(--text-muted);
    line-height: 1.45;
  }
  .specs-section-body {
    padding: var(--space-4) var(--space-5);
  }

  /* ─── Form fields ─── */
  .specs-field { margin-bottom: var(--space-4); }
  .specs-field:last-child { margin-bottom: 0; }
  .specs-field-label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-muted);
    margin-bottom: 6px;
  }
  .specs-input,
  .specs-select,
  .specs-textarea {
    width: 100%;
    box-sizing: border-box;
    padding: 10px 12px;
    font: inherit;
    font-size: 13.5px;
    color: var(--text);
    background: rgba(255,255,255,0.03);
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
  }
  .specs-textarea {
    font-family: var(--font-mono);
    font-size: 13px;
    line-height: 1.55;
    resize: vertical;
    min-height: 200px;
  }
  .specs-input:focus,
  .specs-select:focus,
  .specs-textarea:focus {
    outline: none;
    border-color: rgba(140,109,255,0.55);
    background: rgba(255,255,255,0.05);
    box-shadow: 0 0 0 3px rgba(140,109,255,0.18);
  }
  .specs-select {
    appearance: none;
    padding-right: 30px;
    background-image:
      linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
      linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
    background-position: right 12px top 50%, right 7px top 50%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
  }
  .specs-field-hint {
    margin-top: 6px;
    font-size: 11.5px;
    color: var(--text-muted);
    line-height: 1.45;
  }

  /* ─── Buttons ─── */
  .specs-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding-top: 4px;
  }
  .specs-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 18px;
    border-radius: 10px;
    font-size: 13.5px;
    font-weight: 600;
    text-decoration: none;
    border: 1px solid transparent;
    cursor: pointer;
    font: inherit;
    line-height: 1;
    white-space: nowrap;
    transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease, border-color 120ms ease, color 120ms ease;
  }
  .specs-btn-primary {
    background: linear-gradient(135deg, #8c6dff 0%, #36c5d6 100%);
    color: #ffffff;
    box-shadow: 0 6px 18px -6px rgba(140,109,255,0.50), inset 0 1px 0 rgba(255,255,255,0.16);
  }
  .specs-btn-primary:hover {
    transform: translateY(-1px);
    box-shadow: 0 10px 24px -8px rgba(140,109,255,0.60), inset 0 1px 0 rgba(255,255,255,0.20);
    color: #ffffff;
    text-decoration: none;
  }
  .specs-btn-primary:disabled {
    cursor: not-allowed;
    opacity: 0.6;
    transform: none;
    box-shadow: none;
  }
  .specs-actions-hint {
    font-size: 12px;
    color: var(--text-muted);
  }

  /* ─── How-this-works step cards ─── */
  .specs-steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 10px;
  }
  .specs-step {
    padding: 14px;
    background: rgba(255,255,255,0.018);
    border: 1px solid var(--border);
    border-radius: 11px;
    transition: border-color 120ms ease, background 120ms ease;
  }
  .specs-step:hover {
    border-color: var(--border-strong);
    background: rgba(255,255,255,0.03);
  }
  .specs-step-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px; height: 24px;
    border-radius: 7px;
    background: rgba(140,109,255,0.16);
    color: #c4b5fd;
    box-shadow: inset 0 0 0 1px rgba(140,109,255,0.32);
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 700;
    margin-bottom: 10px;
  }
  .specs-step-title {
    margin: 0 0 4px;
    font-family: var(--font-display);
    font-size: 13.5px;
    font-weight: 700;
    color: var(--text-strong);
    letter-spacing: -0.005em;
  }
  .specs-step-body {
    margin: 0;
    font-size: 12.5px;
    color: var(--text-muted);
    line-height: 1.5;
  }
  .specs-step-body code {
    font-family: var(--font-mono);
    font-size: 11.5px;
    background: rgba(255,255,255,0.04);
    padding: 1px 5px;
    border-radius: 4px;
    color: var(--text);
  }

  /* ─── 403 / not-found ─── */
  .specs-empty {
    max-width: 540px;
    margin: var(--space-8) auto;
    padding: var(--space-6);
    text-align: center;
    background: var(--bg-elevated);
    border: 1px dashed var(--border-strong);
    border-radius: 16px;
  }
  .specs-empty h2 {
    font-family: var(--font-display);
    font-size: 20px;
    margin: 0 0 8px;
    color: var(--text-strong);
  }
  .specs-empty p {
    margin: 0;
    color: var(--text-muted);
    font-size: 13.5px;
  }
`;

interface ResolvedRepo {
  ownerId: string;
  ownerUsername: string;
  repoId: string;
  repoName: string;
  defaultBranch: string;
}

async function resolveRepo(
  ownerName: string,
  repoName: string
): Promise<ResolvedRepo | null> {
  try {
    const [ownerRow] = await db
      .select()
      .from(users)
      .where(eq(users.username, ownerName))
      .limit(1);
    if (!ownerRow) return null;
    const [repoRow] = await db
      .select()
      .from(repositories)
      .where(
        and(
          eq(repositories.ownerId, ownerRow.id),
          eq(repositories.name, repoName)
        )
      )
      .limit(1);
    if (!repoRow) return null;
    return {
      ownerId: ownerRow.id,
      ownerUsername: ownerRow.username,
      repoId: repoRow.id,
      repoName: repoRow.name,
      defaultBranch: repoRow.defaultBranch || "main",
    };
  } catch {
    return null;
  }
}

/**
 * Write access check. Gluecron has no collaborator table yet, so "write
 * access" == repo owner. Matches the convention used by repo-settings and
 * ai-explain's regenerate endpoint.
 */
function hasWriteAccess(
  resolved: ResolvedRepo,
  userId: string | undefined
): boolean {
  return !!userId && resolved.ownerId === userId;
}

/**
 * Build the default spec text for an issue-driven generation. Format:
 *
 *   Implement: <title>
 *
 *   <body>
 *
 *   Closes #<n>
 *
 * The trailing `Closes #N` is picked up by `src/lib/close-keywords.ts` (J7)
 * so the issue auto-closes when the AI-generated PR is merged. Body is
 * trimmed and falls back to an empty string if missing. Pure helper —
 * exported for tests.
 */
export function buildSpecFromIssue(input: {
  number: number;
  title: string;
  body: string | null | undefined;
}): string {
  const title = (input.title || "").trim();
  const body = (input.body || "").trim();
  const lines: string[] = [];
  if (title) lines.push(`Implement: ${title}`);
  if (body) {
    lines.push("");
    lines.push(body);
  }
  lines.push("");
  lines.push(`Closes #${input.number}`);
  return lines.join("\n");
}

function SpecForm({
  ownerName,
  repoName,
  branches,
  defaultBranch,
  spec,
  baseRef,
  error,
  fromIssueNumber,
  fromIssueTitle,
}: {
  ownerName: string;
  repoName: string;
  branches: string[];
  defaultBranch: string;
  spec?: string;
  baseRef?: string;
  error?: string;
  fromIssueNumber?: number;
  fromIssueTitle?: string;
}) {
  const branchList = branches.length > 0 ? branches : [defaultBranch];
  const selectedBase = baseRef && branchList.includes(baseRef)
    ? baseRef
    : defaultBranch;
  return (
    <div class="specs-wrap">
      <header class="specs-head">
        <div class="specs-eyebrow">
          <span class="specs-eyebrow-dot" aria-hidden="true" />
          Repository · Spec to PR
          <span class="specs-pill-experimental">Experimental</span>
        </div>
        <h1 class="specs-title">
          <span class="specs-title-grad">Describe it. Ship a draft.</span>
        </h1>
        <p class="specs-sub">
          Write a feature in plain English. Claude drafts the code changes
          and opens a pull request against the branch you pick. Every PR is{" "}
          <strong>draft by default</strong> — review every line before merging.
        </p>
      </header>

      {fromIssueNumber && (
        <div class="specs-banner is-info" role="status">
          <span class="specs-banner-dot" aria-hidden="true" />
          <span>
            Building from issue{" "}
            <a href={`/${ownerName}/${repoName}/issues/${fromIssueNumber}`}>
              #{fromIssueNumber}
              {fromIssueTitle ? ` — ${fromIssueTitle}` : ""}
            </a>
            . The spec below has been pre-filled and will auto-close the
            issue on merge.
          </span>
        </div>
      )}

      {error && (
        <div class="specs-banner is-error" role="alert">
          <span class="specs-banner-dot" aria-hidden="true" />
          <span>{error}</span>
        </div>
      )}

      <section class="specs-section">
        <header class="specs-section-head">
          <h2 class="specs-section-title">Feature spec</h2>
          <p class="specs-section-sub">
            One sentence or a paragraph. Be specific about files, behaviour,
            or success criteria when you can.
          </p>
        </header>
        <div class="specs-section-body">
          <form
            method="post"
            action={`/${ownerName}/${repoName}/spec`}
            id="spec-form"
          >
            <div class="specs-field">
              <label class="specs-field-label" for="spec">What do you want built?</label>
              <textarea
                class="specs-textarea"
                name="spec"
                id="spec"
                rows={10}
                required
                placeholder="add a dark mode toggle to the settings page"
              >{spec || ""}</textarea>
            </div>

            <div class="specs-field">
              <label class="specs-field-label" for="baseRef">Base branch</label>
              <select
                class="specs-select"
                name="baseRef"
                id="baseRef"
                value={selectedBase}
              >
                {branchList.map((b) => (
                  <option value={b} selected={b === selectedBase}>
                    {b}
                  </option>
                ))}
              </select>
              <p class="specs-field-hint">
                The draft PR will target this branch. Nothing lands without
                your approval.
              </p>
            </div>

            <div class="specs-actions">
              <button type="submit" class="specs-btn specs-btn-primary">
                Generate PR with AI
              </button>
              <span class="specs-actions-hint">Typically 10-30 seconds.</span>
            </div>
          </form>
        </div>
      </section>

      <section class="specs-section">
        <header class="specs-section-head">
          <h2 class="specs-section-title">How this works</h2>
          <p class="specs-section-sub">
            Three steps from spec to mergeable diff — all observable, all
            reversible.
          </p>
        </header>
        <div class="specs-section-body">
          <div class="specs-steps">
            <div class="specs-step">
              <span class="specs-step-num">1</span>
              <h3 class="specs-step-title">You write a spec.</h3>
              <p class="specs-step-body">
                A sentence or a paragraph describing the change you want.
              </p>
            </div>
            <div class="specs-step">
              <span class="specs-step-num">2</span>
              <h3 class="specs-step-title">Claude drafts the diff.</h3>
              <p class="specs-step-body">
                We fetch the base branch, run Claude against the repo, and
                commit the proposed changes to a new branch.
              </p>
            </div>
            <div class="specs-step">
              <span class="specs-step-num">3</span>
              <h3 class="specs-step-title">A draft PR opens.</h3>
              <p class="specs-step-body">
                You review, edit, and merge on your terms. Nothing lands on{" "}
                <code>{selectedBase}</code> automatically.
              </p>
            </div>
          </div>
        </div>
      </section>

      <script dangerouslySetInnerHTML={{ __html: DISABLE_ON_SUBMIT_JS }} />
      <style dangerouslySetInnerHTML={{ __html: specsStyles }} />
    </div>
  );
}

specs.get("/:owner/:repo/spec", softAuth, requireAuth, async (c) => {
  const { owner, repo } = c.req.param();
  const user = c.get("user")!;

  const resolved = await resolveRepo(owner, repo);
  if (!resolved) {
    return c.html(
      <Layout title="Not Found" user={user}>
        <div class="specs-empty">
          <h2>Repository not found</h2>
          <p>No such repository.</p>
        </div>
        <style dangerouslySetInnerHTML={{ __html: specsStyles }} />
      </Layout>,
      404
    );
  }

  if (!hasWriteAccess(resolved, user.id)) {
    return c.html(
      <Layout title="Forbidden" user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <div class="specs-empty">
          <h2>Write access required</h2>
          <p>You need write access to generate a spec-to-PR on this repository.</p>
        </div>
        <style dangerouslySetInnerHTML={{ __html: specsStyles }} />
      </Layout>,
      403
    );
  }

  let branches: string[] = [];
  try {
    branches = await listBranches(owner, repo);
  } catch {
    branches = [];
  }

  // Optional: pre-fill from an issue. Triggered from the "Build with AI"
  // button on the issue detail page. Silently no-ops on missing/unknown
  // issue so the form still renders.
  let prefilledSpec: string | undefined;
  let fromIssueNumber: number | undefined;
  let fromIssueTitle: string | undefined;
  const fromIssueRaw = c.req.query("fromIssue");
  if (fromIssueRaw) {
    const n = Number.parseInt(fromIssueRaw, 10);
    if (Number.isInteger(n) && n > 0) {
      try {
        const [issueRow] = await db
          .select()
          .from(issues)
          .where(
            and(eq(issues.repositoryId, resolved.repoId), eq(issues.number, n))
          )
          .limit(1);
        if (issueRow) {
          fromIssueNumber = issueRow.number;
          fromIssueTitle = issueRow.title;
          prefilledSpec = buildSpecFromIssue({
            number: issueRow.number,
            title: issueRow.title,
            body: issueRow.body,
          });
        }
      } catch {
        // Pre-fill is a convenience, never block form render.
      }
    }
  }

  return c.html(
    <Layout title={`Spec to PR — ${owner}/${repo}`} user={user}>
      <RepoHeader owner={owner} repo={repo} />
      <SpecForm
        ownerName={owner}
        repoName={repo}
        branches={branches}
        defaultBranch={resolved.defaultBranch}
        spec={prefilledSpec}
        fromIssueNumber={fromIssueNumber}
        fromIssueTitle={fromIssueTitle}
      />
    </Layout>
  );
});

specs.post("/:owner/:repo/spec", softAuth, requireAuth, async (c) => {
  const { owner, repo } = c.req.param();
  const user = c.get("user")!;

  const resolved = await resolveRepo(owner, repo);
  if (!resolved) return c.notFound();

  if (!hasWriteAccess(resolved, user.id)) {
    return c.html(
      <Layout title="Forbidden" user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <div class="specs-empty">
          <h2>Write access required</h2>
          <p>You need write access to generate a spec-to-PR on this repository.</p>
        </div>
        <style dangerouslySetInnerHTML={{ __html: specsStyles }} />
      </Layout>,
      403
    );
  }

  const body = await c.req.parseBody();
  const spec = String(body.spec || "").trim();
  const baseRef = String(body.baseRef || resolved.defaultBranch).trim()
    || resolved.defaultBranch;

  let branches: string[] = [];
  try {
    branches = await listBranches(owner, repo);
  } catch {
    branches = [];
  }

  function renderWithError(error: string, status: 400 | 500 | 503 = 400) {
    return c.html(
      <Layout title={`Spec to PR — ${owner}/${repo}`} user={user}>
        <RepoHeader owner={owner} repo={repo} />
        <SpecForm
          ownerName={owner}
          repoName={repo}
          branches={branches}
          defaultBranch={resolved!.defaultBranch}
          spec={spec}
          baseRef={baseRef}
          error={error}
        />
      </Layout>,
      status
    );
  }

  if (!spec) {
    return renderWithError("Spec is required.");
  }

  // Dynamically import the backend so this file works even before the
  // sibling branch landing `src/lib/spec-to-pr.ts` is merged. If the module
  // is missing or throws we surface a soft error instead of 500-ing.
  let createSpecPR:
    | ((args: {
        repoId: string;
        spec: string;
        baseRef: string;
        userId: string;
      }) => Promise<
        | { ok: true; prNumber: number }
        | { ok: false; error: string }
      >)
    | null = null;
  try {
    const mod: any = await import("../lib/spec-to-pr");
    createSpecPR =
      (mod && (mod.createSpecPR || (mod.default && mod.default.createSpecPR))) ||
      null;
  } catch {
    createSpecPR = null;
  }

  if (!createSpecPR) {
    return renderWithError(
      "Backend not available — spec-to-PR is not deployed yet. Please try again later.",
      503
    );
  }

  let result:
    | { ok: true; prNumber: number }
    | { ok: false; error: string };
  try {
    result = await createSpecPR({
      repoId: resolved.repoId,
      spec,
      baseRef,
      userId: user.id,
    });
  } catch (err) {
    const msg =
      err instanceof Error ? err.message : "Unexpected error generating PR.";
    return renderWithError(`Failed to generate PR: ${msg}`, 500);
  }

  if (!result || !result.ok) {
    const msg = (result && "error" in result && result.error) || "Unknown error.";
    return renderWithError(`Failed to generate PR: ${msg}`);
  }

  return c.redirect(`/${owner}/${repo}/pulls/${result.prNumber}`);
});

export default specs;
