import { createCoreAPIClient } from "@/lib/features/coreClient";
import type { CoreResponse } from "@/lib/features/types";
import type {
  EDMTemplate,
  CreateEDMInput,
  UpdateEDMInput,
  EDMFolder,
  EDMAssetEntry,
  EDMImportResult,
  EDMResolveResult,
  ImageScanReport,
  BatchImportResult,
  EDMStorageTargetValue,
} from "./types";

const coreClient = createCoreAPIClient();

const BASE = "/v1/admin-console/edm";

export async function createEDMTemplate(
  input: CreateEDMInput,
): Promise<CoreResponse<EDMTemplate>> {
  return coreClient<EDMTemplate>("/v1/admin-console/edm", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function updateEDMTemplate(
  templateId: string,
  input: UpdateEDMInput,
): Promise<CoreResponse<EDMTemplate>> {
  return coreClient<EDMTemplate>(`/v1/admin-console/edm/${templateId}`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

export async function deleteEDMTemplate(
  templateId: string,
): Promise<CoreResponse<null>> {
  return coreClient<null>(`/v1/admin-console/edm/${templateId}`, {
    method: "DELETE",
  });
}

export async function sendEDMTestEmail(
  templateId: string,
  to: string,
): Promise<CoreResponse<null>> {
  return coreClient<null>(`/v1/admin-console/edm/${templateId}/send-test`, {
    method: "POST",
    body: JSON.stringify({ to }),
  });
}

// ── folders ─────────────────────────────────────────────────────────────────

export async function createEDMFolder(input: {
  name: string;
  parent_id?: string | null;
}): Promise<CoreResponse<EDMFolder>> {
  return coreClient<EDMFolder>(`${BASE}/folders`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function updateEDMFolder(
  folderId: string,
  input: { name?: string; parent_id?: string | null },
): Promise<CoreResponse<EDMFolder>> {
  return coreClient<EDMFolder>(`${BASE}/folders/${folderId}`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

/**
 * Delete a folder and decide what happens to its contents.
 *
 *   orphan  (default) contents move to the top level
 *   move    contents move into `targetFolderId`
 *   cascade contents are DELETED — irreversible, and templates are referenced
 *           by live mail, so this is never the default
 */
export async function deleteEDMFolder(
  folderId: string,
  opts: {
    mode?: "orphan" | "move" | "cascade";
    targetFolderId?: string | null;
  } = {},
): Promise<
  CoreResponse<{
    mode: string;
    moved?: { templates: number; folders: number };
    deletedTemplates?: number;
  }>
> {
  const qs = new URLSearchParams({ mode: opts.mode ?? "orphan" });
  if (opts.mode === "move" && opts.targetFolderId)
    qs.set("target", opts.targetFolderId);
  return coreClient(`${BASE}/folders/${folderId}?${qs.toString()}`, {
    method: "DELETE",
  });
}

/**
 * Empty a folder's images/ subfolder. Templates are untouched.
 *
 * Irreversible: any of these URLs may already be embedded in delivered mail.
 */
export async function deleteEDMFolderImages(
  folderId: string,
): Promise<CoreResponse<{ folderId: string; deleted: number }>> {
  return coreClient(`${BASE}/folders/${folderId}/images`, { method: "DELETE" });
}

// ── preflight & assets ──────────────────────────────────────────────────────

/** Analyse html without storing anything — safe to call on every edit pause. */
export async function scanEDMImages(
  html: string,
): Promise<CoreResponse<ImageScanReport>> {
  return coreClient<ImageScanReport>(`${BASE}/scan-images`, {
    method: "POST",
    body: JSON.stringify({ html }),
  });
}

/** Extract inline data-URIs to the asset bucket and rewrite them. */
export async function resolveEDMHtml(
  html: string,
): Promise<CoreResponse<EDMResolveResult>> {
  return coreClient<EDMResolveResult>(`${BASE}/resolve`, {
    method: "POST",
    body: JSON.stringify({ html }),
  });
}

/**
 * Multipart uploads bypass coreClient deliberately.
 *
 * coreClient pins Content-Type: application/json on every non-GET, which would
 * overwrite the multipart boundary the browser generates and make the body
 * unparseable server-side. XHR also gives upload progress, which fetch still
 * cannot. Mirrors uploadDashboardVersion in features/dashboard/action.ts.
 */
function multipartPost<T>(
  path: string,
  form: FormData,
  onProgress?: (percent: number) => void,
): Promise<CoreResponse<T> & { errors?: string[] }> {
  return new Promise((resolve) => {
    const xhr = new XMLHttpRequest();
    xhr.open("POST", `/api/proxy${path}`);
    xhr.withCredentials = true;

    xhr.upload.onprogress = (e) => {
      if (e.lengthComputable && onProgress) {
        onProgress(Math.round((e.loaded / e.total) * 100));
      }
    };

    xhr.onload = () => {
      try {
        const body = JSON.parse(xhr.responseText);
        resolve({
          ...body,
          success:
            xhr.status >= 200 && xhr.status < 300 && body?.success !== false,
          errors: Array.isArray(body?.errors) ? body.errors : body?.error,
        });
      } catch {
        resolve({
          success: false,
          message: `Upload failed (HTTP ${xhr.status})`,
          data: null as never,
        });
      }
    };
    xhr.onerror = () =>
      resolve({
        success: false,
        message: "Upload failed — check your connection and try again.",
        data: null as never,
      });

    xhr.send(form);
  });
}

/**
 * Upload one image.
 *
 * `folderId` places the object under that folder's prefix in the bucket, which
 * is what lets a folder's images be deleted as a unit. Omit it and the image
 * lands at the asset root, outside any folder's reach.
 */
export function uploadEDMAsset(
  file: File,
  folderId?: string | null,
  onProgress?: (percent: number) => void,
): Promise<CoreResponse<EDMAssetEntry>> {
  const form = new FormData();
  form.append("image", file);
  if (folderId) form.append("folder_id", folderId);
  return multipartPost<EDMAssetEntry>(`${BASE}/assets`, form, onProgress);
}

/**
 * Delete one image from storage.
 *
 * Irreversible. The response reports which templates referenced it so the
 * caller learns what they just broke, rather than the request being refused —
 * a manifest is a lower bound, so refusing would be both incomplete and
 * unhelpful when the image is genuinely unused.
 */
export async function deleteEDMAsset(
  path: string,
): Promise<CoreResponse<{ path: string; wasUsedBy: string[] }>> {
  return coreClient(`${BASE}/assets?path=${encodeURIComponent(path)}`, {
    method: "DELETE",
  });
}

/**
 * Upload a designer's zip (index.html + images/) or a bare .html and get back
 * html whose image references are all public URLs. Stores nothing as a template
 * — the caller reviews the result and then creates it — so re-importing is safe.
 */
export function importEDMBundle(
  file: File,
  onProgress?: (percent: number) => void,
): Promise<CoreResponse<EDMImportResult> & { errors?: string[] }> {
  const form = new FormData();
  form.append("bundle", file);
  return multipartPost<EDMImportResult>(`${BASE}/import`, form, onProgress);
}

/**
 * Bulk import: one zip holding many EDMs, each in its own directory with its
 * own images folder. Mirrors that layout into folders and creates one template
 * per html.
 *
 * Unlike importEDMBundle this COMMITS. Entries with a broken image reference
 * are still created but left unpublished, so nothing half-finished can be sent.
 */
export function importEDMBundleBatch(
  file: File,
  opts: { parentFolderId?: string | null; rootFolderName?: string | null } = {},
  onProgress?: (percent: number) => void,
): Promise<CoreResponse<BatchImportResult> & { errors?: string[] }> {
  const form = new FormData();
  form.append("bundle", file);
  if (opts.parentFolderId) form.append("parent_folder_id", opts.parentFolderId);
  if (opts.rootFolderName) form.append("root_folder_name", opts.rootFolderName);
  return multipartPost<BatchImportResult>(
    `${BASE}/import-batch`,
    form,
    onProgress,
  );
}

/**
 * Choose where NEW EDMs are stored. Super admin only, enforced server-side.
 *
 * Does not move anything: templates already in GCS stay in GCS, templates that
 * only exist in SendGrid stay in SendGrid, and each keeps being edited and
 * deleted where it lives.
 */
export async function setEDMStorageTarget(
  target: EDMStorageTargetValue,
): Promise<CoreResponse<{ target: EDMStorageTargetValue; source: string }>> {
  return coreClient(`${BASE}/storage-target`, {
    method: "PUT",
    body: JSON.stringify({ target }),
  });
}
