import { get, writable } from "svelte/store";
import {
  createFragment as createFragmentCommand,
  deleteFragment as deleteFragmentCommand,
  listFragments,
  updateFragment as updateFragmentCommand,
  type FragmentDto,
} from "$lib/backend/fragments";

export type FragmentItem = {
  id: string;
  label: string;
  initialContent: string;
};

export type ParsedFragment = {
  title: string;
  type: string;
  tags: string[];
  body: string;
};

const initialFragments: FragmentItem[] = [];

export const fragmentsStore = writable<FragmentItem[]>(initialFragments);
export const fragmentsBusyStore = writable(false);

function toStoreId(id: string): string {
  return `fragments/${id}`;
}

function toBackendId(id: string): string | null {
  const prefix = "fragments/";
  if (!id.startsWith(prefix)) return null;
  const backendId = id.slice(prefix.length).trim();
  return backendId || null;
}

function splitDescription(description: string): {
  title: string;
  body: string;
} {
  const normalized = description.trim();
  if (!normalized) {
    return { title: "Untitled Fragment", body: "" };
  }

  const separator = normalized.indexOf("\n\n");
  if (separator === -1) {
    return { title: normalized, body: "" };
  }

  const title = normalized.slice(0, separator).trim() || "Untitled Fragment";
  const body = normalized.slice(separator + 2).trim();
  return { title, body };
}

function composeDescription(title: string, body: string): string {
  const resolvedTitle = title.trim() || "Untitled Fragment";
  const resolvedBody = body.trim() || "Add details for this fragment.";
  return `${resolvedTitle}\n\n${resolvedBody}`;
}

function dtoToItem(dto: FragmentDto): FragmentItem {
  const parsed = splitDescription(dto.description);
  return {
    id: toStoreId(dto.id),
    label: parsed.title,
    initialContent: serializeFragment({
      title: parsed.title,
      type: dto.type,
      tags: dto.tags ?? [],
      body: parsed.body,
    }),
  };
}

function upsertById(items: FragmentItem[], next: FragmentItem): FragmentItem[] {
  const idx = items.findIndex((item) => item.id === next.id);
  if (idx === -1) {
    return [next, ...items];
  }
  const clone = [...items];
  clone[idx] = next;
  return clone;
}

export function createFragmentId(title: string): string {
  const slug = title
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 40);
  return `fragments/${slug || "fragment"}-${Date.now()}`;
}

export function serializeFragment(payload: ParsedFragment): string {
  const title = payload.title.trim() || "Untitled Fragment";
  const type = payload.type.trim();
  const tagsLine = payload.tags.length
    ? payload.tags.map((tag) => `#${tag}`).join(" ")
    : "(none)";
  const body = payload.body.trim() || "Add details for this fragment.";
  return `# ${title}\n\nType: ${type}\n\nTags: ${tagsLine}\n\n${body}`;
}

export function parseFragmentContent(
  content: string,
  fallbackTitle = "Untitled Fragment",
): ParsedFragment {
  const headingMatch = content.match(/^#\s+(.+)$/m);
  const typeMatch = content.match(/^Type:\s*(.+)$/m);
  const tagsMatch = content.match(/^Tags:\s*(.+)$/m);
  const bodyMatch = content.match(/^#.*\n\nType:.*\n\nTags:.*\n\n([\s\S]*)$/);

  const rawTags = tagsMatch?.[1]?.trim() ?? "(none)";
  const tags =
    rawTags.toLowerCase() === "(none)"
      ? []
      : rawTags
          .split(/\s+/)
          .map((tag) => tag.replace(/^#/, "").trim())
          .filter(Boolean);

  return {
    title: headingMatch?.[1]?.trim() || fallbackTitle,
    type: typeMatch?.[1]?.trim() || "",
    tags,
    body: bodyMatch?.[1]?.trim() || "",
  };
}

export function createFragmentDraft(): FragmentItem {
  const id = `fragments/new-${Date.now()}`;
  return {
    id,
    label: "New Fragment",
    initialContent: "# New Fragment\n\nType: \n\nTags: (none)\n\n",
  };
}

export function createFragmentItem(
  title: string,
  content: string,
): FragmentItem {
  return {
    id: createFragmentId(title),
    label: title.trim() || "Untitled Fragment",
    initialContent: content,
  };
}

export function updateFragmentItem(
  items: FragmentItem[],
  id: string,
  title: string,
  content: string,
): FragmentItem[] {
  return items.map((item) =>
    item.id === id
      ? {
          ...item,
          label: title.trim() || "Untitled Fragment",
          initialContent: content,
        }
      : item,
  );
}

export function prependFragmentItem(
  items: FragmentItem[],
  item: FragmentItem,
): FragmentItem[] {
  return [item, ...items];
}

export function removeFragmentItem(
  items: FragmentItem[],
  id: string,
): FragmentItem[] {
  return items.filter((item) => item.id !== id);
}

export async function hydrateFragments(): Promise<void> {
  fragmentsBusyStore.set(true);
  try {
    const items = await listFragments();
    fragmentsStore.set(items.map(dtoToItem));
  } catch (error) {
    console.error("[fragments] hydrate:error", error);
    throw error;
  } finally {
    fragmentsBusyStore.set(false);
  }
}

export async function createFragmentFromParsed(
  payload: ParsedFragment,
): Promise<FragmentItem> {
  const created = await createFragmentCommand({
    type: payload.type.trim(),
    description: composeDescription(payload.title, payload.body),
    tags: payload.tags,
  });
  const item = dtoToItem(created);
  fragmentsStore.update((items) => prependFragmentItem(items, item));
  return item;
}

export async function updateFragmentFromParsed(
  storeId: string,
  payload: ParsedFragment,
): Promise<FragmentItem | null> {
  const backendId = toBackendId(storeId);
  if (!backendId) return null;

  const ok = await updateFragmentCommand(backendId, {
    type: payload.type.trim(),
    description: composeDescription(payload.title, payload.body),
    tags: payload.tags,
  });
  if (!ok) return null;

  const item: FragmentItem = {
    id: storeId,
    label: payload.title.trim() || "Untitled Fragment",
    initialContent: serializeFragment(payload),
  };
  fragmentsStore.update((items) => upsertById(items, item));
  return item;
}

export async function deleteFragmentByStoreId(
  storeId: string,
): Promise<boolean> {
  const backendId = toBackendId(storeId);
  if (!backendId) return false;

  const ok = await deleteFragmentCommand(backendId);
  if (!ok) return false;
  fragmentsStore.update((items) => removeFragmentItem(items, storeId));
  return true;
}

export function hasFragment(storeId: string): boolean {
  return get(fragmentsStore).some((item) => item.id === storeId);
}
