<template>
  <UiDialog>
    <UiDialogTrigger as-child>
      <UiButton
        class="gap-x-1.5 w-full h-8 justify-start text-primary"
        variant="ghost"
        size="sm"
      >
        <span class="i-material-symbols:auto-awesome text-base" />
        AI Tailor with Signal
      </UiButton>
    </UiDialogTrigger>

    <UiDialogContent class="sm:max-w-130">
      <UiDialogHeader>
        <UiDialogTitle>Tailor this Markdown resume</UiDialogTitle>
        <UiDialogDescription>
          Signal reads the current editor buffer, maps every claim to evidence, then
          rewrites it for one role. Your YAML header and editor styles stay intact.
        </UiDialogDescription>
      </UiDialogHeader>

      <div class="space-y-4 pt-2 text-sm">
        <div class="grid grid-cols-2 gap-1 rounded-lg bg-muted p-1">
          <UiButton
            size="sm"
            :variant="mode === 'url' ? 'secondary' : 'ghost'"
            @click="mode = 'url'"
          >
            Job URL
          </UiButton>
          <UiButton
            size="sm"
            :variant="mode === 'text' ? 'secondary' : 'ghost'"
            @click="mode = 'text'"
          >
            Paste description
          </UiButton>
        </div>

        <div v-if="mode === 'url'" class="space-y-2">
          <label for="signal-job-url" class="font-medium">Public job-post URL</label>
          <UiInput
            id="signal-job-url"
            v-model="jobURL"
            type="url"
            placeholder="https://company.com/careers/role"
          />
        </div>

        <div v-else class="space-y-2">
          <label for="signal-job-text" class="font-medium"
            >Complete job description</label
          >
          <textarea
            id="signal-job-text"
            v-model="jobText"
            rows="8"
            class="w-full resize-y rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring"
            placeholder="Paste responsibilities and qualifications…"
          />
          <div class="text-right text-xs text-muted-foreground">
            {{ jobText.length.toLocaleString() }} characters
          </div>
        </div>

        <div class="space-y-2">
          <label for="signal-about" class="font-medium">
            Additional factual context
            <span class="font-normal text-muted-foreground">(optional)</span>
          </label>
          <textarea
            id="signal-about"
            v-model="about"
            rows="3"
            class="w-full resize-y rounded-md border bg-background px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-ring"
            placeholder="Add relevant facts that are not yet written in the resume…"
          />
        </div>

        <div class="space-y-3 rounded-lg border bg-muted/40 p-3">
          <div class="flex items-start justify-between gap-4">
            <div>
              <label for="signal-strength" class="font-medium">
                Tailoring strength
              </label>
              <p class="mt-1 text-xs text-muted-foreground">
                {{ strengthDescription }}
              </p>
            </div>
            <span
              class="min-w-10 rounded-md bg-primary px-2 py-1 text-center text-xs font-semibold text-primary-foreground"
            >
              {{ tailoringStrength }}
            </span>
          </div>
          <input
            id="signal-strength"
            v-model.number="tailoringStrength"
            class="w-full cursor-pointer accent-primary"
            type="range"
            min="0"
            max="100"
            step="5"
          />
          <div class="flex justify-between text-xs text-muted-foreground">
            <span>Source-faithful</span>
            <span>Maximum truthful fit</span>
          </div>
          <p class="border-t pt-2 text-xs text-muted-foreground">
            Strength changes wording and supported detail only. It never permits invented
            qualifications or achievements.
          </p>
        </div>

        <div class="space-y-3 rounded-lg border p-3">
          <div>
            <div class="font-medium">Tailoring configuration</div>
            <p class="mt-1 text-xs text-muted-foreground">
              Set the app-wide evidence guard, then choose the mode for this rewrite.
            </p>
          </div>
          <button
            class="flex w-full items-center gap-3 rounded-md border p-3 text-left"
            :class="
              globalEvidenceGuard
                ? 'border-emerald-600 bg-emerald-50'
                : 'border-orange-500 bg-orange-50'
            "
            type="button"
            :aria-pressed="globalEvidenceGuard"
            @click="toggleGlobalEvidenceGuard"
          >
            <span
              class="relative h-5 w-9 shrink-0 rounded-full transition-colors"
              :class="globalEvidenceGuard ? 'bg-emerald-600' : 'bg-orange-500'"
            >
              <span
                class="absolute left-0.5 top-0.5 size-4 rounded-full bg-white shadow transition-transform"
                :class="globalEvidenceGuard ? 'translate-x-4' : ''"
              />
            </span>
            <span>
              <strong class="block text-xs">
                {{
                  globalEvidenceGuard
                    ? "App-wide evidence guard on"
                    : "App-wide evidence guard off"
                }}
              </strong>
              <small class="mt-0.5 block text-xs text-muted-foreground">
                {{
                  globalEvidenceGuard
                    ? "Claim-level evidence rules are available across Signal."
                    : "All tailoring and revision uses profile-based rules."
                }}
              </small>
            </span>
          </button>
          <button
            class="flex w-full items-center gap-3 rounded-md border bg-muted/40 p-3 text-left"
            :class="evidenceMode === 'profile' ? 'border-orange-500 bg-orange-50' : ''"
            type="button"
            :disabled="!globalEvidenceGuard"
            :aria-pressed="evidenceMode === 'profile'"
            :aria-disabled="!globalEvidenceGuard"
            @click="evidenceMode = evidenceMode === 'strict' ? 'profile' : 'strict'"
          >
            <span
              class="relative h-5 w-9 shrink-0 rounded-full transition-colors"
              :class="evidenceMode === 'profile' ? 'bg-orange-500' : 'bg-gray-400'"
            >
              <span
                class="absolute left-0.5 top-0.5 size-4 rounded-full bg-white shadow transition-transform"
                :class="evidenceMode === 'profile' ? 'translate-x-4' : ''"
              />
            </span>
            <span>
              <strong class="block text-xs">
                {{
                  evidenceMode === "profile"
                    ? "Flexible profile-based rewrite"
                    : "Strict source evidence"
                }}
              </strong>
              <small class="mt-0.5 block text-xs text-muted-foreground">
                {{
                  evidenceMode === "profile"
                    ? globalEvidenceGuard
                      ? "Uses the full resume profile; claim-level IDs are optional."
                      : "Forced by the app-wide setting; claim-level IDs are optional."
                    : "Every claim must cite a supporting profile fact."
                }}
              </small>
            </span>
          </button>
          <p class="text-xs text-muted-foreground">
            Flexible mode still excludes employers, skills, dates, metrics, and
            achievements that are absent from this resume or your added context.
          </p>
        </div>

        <UiAlert v-if="errorMessage" variant="destructive">
          <UiAlertTitle>Signal could not complete the request</UiAlertTitle>
          <UiAlertDescription>{{ errorMessage }}</UiAlertDescription>
        </UiAlert>

        <UiAlert v-if="successMessage">
          <UiAlertTitle>Done</UiAlertTitle>
          <UiAlertDescription>{{ successMessage }}</UiAlertDescription>
        </UiAlert>

        <div class="rounded-lg border bg-muted/40 p-3 text-xs text-muted-foreground">
          Before applying AI changes, Signal stores an undo snapshot in this browser.
          Tailored Markdown is also saved automatically in Oh My CV.
        </div>
      </div>

      <UiDialogFooter class="gap-2 sm:justify-between">
        <div class="flex gap-2">
          <UiButton
            variant="outline"
            size="sm"
            :disabled="busy"
            @click="buildCareerProfile"
          >
            <span class="i-material-symbols:person-search-outline mr-1.5" />
            Learn this resume
          </UiButton>
          <UiButton
            v-if="hasBackup"
            variant="ghost"
            size="sm"
            :disabled="busy"
            @click="undoTailoring"
          >
            Undo AI edit
          </UiButton>
        </div>

        <UiButton :disabled="busy || !hasJobInput" @click="tailorResume">
          <span
            :class="
              busy ? 'i-line-md:loading-twotone-loop' : 'i-material-symbols:auto-awesome'
            "
            class="mr-1.5"
          />
          {{ busy ? "Working…" : "Tailor & apply" }}
        </UiButton>
      </UiDialogFooter>
    </UiDialogContent>
  </UiDialog>
</template>

<script lang="ts" setup>
type TailorResponse = {
  markdown: string;
  package: {
    job: {
      role_title: string | null;
      company: string | null;
    };
  };
};

const { data, setAndSyncToMonaco } = useDataStore();
const { styles } = useStyleStore();
const config = useRuntimeConfig();

const mode = ref<"url" | "text">("url");
const jobURL = ref("");
const jobText = ref("");
const about = ref("");
const tailoringStrength = ref(50);
const evidenceMode = ref<"strict" | "profile">("strict");
const globalEvidenceGuard = ref(true);
const busy = ref(false);
const errorMessage = ref("");
const successMessage = ref("");
const hasBackup = ref(false);

const apiBase = computed(() =>
  String(config.public.signalApiBase || "").replace(/\/$/, "")
);
const backupKey = computed(() => `signal:markdown-backup:${data.resumeId ?? "draft"}`);
const hasJobInput = computed(() =>
  mode.value === "url"
    ? jobURL.value.trim().length > 0
    : jobText.value.trim().length >= 80
);
const strengthDescription = computed(() => {
  if (tailoringStrength.value <= 20) {
    return "Minimal edits that stay close to the current wording and structure.";
  }
  if (tailoringStrength.value <= 70) {
    return "Balanced rewriting using only evidence from this resume.";
  }
  return "Fuller supported detail and stronger job-aligned wording without invention.";
});

onMounted(async () => {
  hasBackup.value = Boolean(localStorage.getItem(backupKey.value));
  try {
    const response = await fetch(`${apiBase.value}/api/settings`);
    if (!response.ok) return;
    const settings = (await response.json()) as { evidence_guard: boolean };
    globalEvidenceGuard.value = settings.evidence_guard;
    if (!settings.evidence_guard) evidenceMode.value = "profile";
  } catch {
    // The API error will be surfaced if the user starts a tailoring request.
  }
});

const toggleGlobalEvidenceGuard = async () => {
  const previous = globalEvidenceGuard.value;
  const next = !previous;
  globalEvidenceGuard.value = next;
  if (!next) evidenceMode.value = "profile";
  errorMessage.value = "";

  try {
    const response = await fetch(`${apiBase.value}/api/settings`, {
      method: "PUT",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ evidence_guard: next })
    });
    const settings = (await response.json()) as {
      evidence_guard?: boolean;
      detail?: string;
    };
    if (!response.ok) {
      throw new Error(settings.detail || "Could not update the Signal settings.");
    }
    globalEvidenceGuard.value = Boolean(settings.evidence_guard);
    if (!globalEvidenceGuard.value) evidenceMode.value = "profile";
    successMessage.value = globalEvidenceGuard.value
      ? "Evidence guard enabled across Signal."
      : "Evidence guard disabled across Signal; profile truth rules remain active.";
  } catch (error) {
    globalEvidenceGuard.value = previous;
    errorMessage.value = error instanceof Error ? error.message : "Unknown error.";
  }
};

async function post<T>(path: string, payload: Record<string, unknown>): Promise<T> {
  const response = await fetch(`${apiBase.value}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload)
  });

  const body = (await response.json()) as T & { detail?: string };
  if (!response.ok) throw new Error(body.detail || "The Signal API returned an error.");
  return body;
}

async function postTask<T>(path: string, payload: Record<string, unknown>): Promise<T> {
  const started = await post<{ task_id: string }>(`${path}/start`, payload);
  let consecutiveNetworkErrors = 0;

  while (true) {
    await new Promise((resolve) => window.setTimeout(resolve, 1200));
    try {
      const response = await fetch(`${apiBase.value}/api/tasks/${started.task_id}`);
      if (!response.ok) {
        const body = (await response.json()) as { detail?: string };
        throw new Error(body.detail || "Could not read the model task status.");
      }
      const task = (await response.json()) as {
        status: "running" | "succeeded" | "failed";
        result?: T;
        error?: string;
      };
      consecutiveNetworkErrors = 0;
      if (task.status === "succeeded" && task.result) return task.result;
      if (task.status === "failed") {
        throw new Error(task.error || "The background model task failed.");
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      if (!message.includes("NetworkError") && !message.includes("Failed to fetch")) {
        throw error;
      }
      consecutiveNetworkErrors += 1;
      if (consecutiveNetworkErrors >= 50) {
        throw new Error(
          `Lost contact with Signal while task ${started.task_id} was running. ` +
            "Check the server stdout logs; the model task may still be active."
        );
      }
    }
  }
}

const jobPayload = () => ({
  job_url: mode.value === "url" ? jobURL.value.trim() : null,
  job_text: mode.value === "text" ? jobText.value.trim() : null
});

const buildCareerProfile = async () => {
  busy.value = true;
  errorMessage.value = "";
  successMessage.value = "";
  try {
    const profile = await postTask<{ facts: unknown[] }>("/api/markdown/profile", {
      markdown: data.markdown,
      about: about.value
    });
    successMessage.value = `Career profile updated with ${profile.facts.length} evidence-backed facts.`;
  } catch (error) {
    errorMessage.value = error instanceof Error ? error.message : "Unknown error.";
  } finally {
    busy.value = false;
  }
};

const tailorResume = async () => {
  busy.value = true;
  errorMessage.value = "";
  successMessage.value = "";
  try {
    localStorage.setItem(
      backupKey.value,
      JSON.stringify({ markdown: data.markdown, savedAt: new Date().toISOString() })
    );
    hasBackup.value = true;

    const result = await postTask<TailorResponse>("/api/markdown/tailor", {
      markdown: data.markdown,
      about: about.value,
      tailoring_strength: tailoringStrength.value,
      evidence_mode: evidenceMode.value,
      ...jobPayload()
    });

    setAndSyncToMonaco("markdown", result.markdown);
    if (data.resumeId) {
      await storageService.updateResume({
        id: data.resumeId,
        name: data.resumeName,
        markdown: result.markdown,
        css: data.css,
        styles: toRaw(styles)
      });
    }

    const role = result.package.job.role_title || "the target role";
    const company = result.package.job.company ? ` at ${result.package.job.company}` : "";
    successMessage.value = `Applied an audited version tailored for ${role}${company}.`;
  } catch (error) {
    errorMessage.value = error instanceof Error ? error.message : "Unknown error.";
  } finally {
    busy.value = false;
  }
};

const undoTailoring = async () => {
  const raw = localStorage.getItem(backupKey.value);
  if (!raw) return;

  try {
    const backup = JSON.parse(raw) as { markdown: string };
    setAndSyncToMonaco("markdown", backup.markdown);
    if (data.resumeId) {
      await storageService.updateResume({
        id: data.resumeId,
        name: data.resumeName,
        markdown: backup.markdown,
        css: data.css,
        styles: toRaw(styles)
      });
    }
    localStorage.removeItem(backupKey.value);
    hasBackup.value = false;
    successMessage.value = "The pre-AI Markdown version has been restored.";
    errorMessage.value = "";
  } catch {
    errorMessage.value = "The stored undo snapshot could not be restored.";
  }
};
</script>
