import { sendCommand } from "./client";
import {
  normalizeFragment,
  type FragmentDto,
  type FragmentDtoRaw,
} from "./fragments";
import { pickCase } from "./normalize";

export type ParsedSectionDto = {
  title: string;
  content: string[];
  checkboxes: Record<string, boolean>;
};

export type JournalEntryDto = {
  date: string;
  fragments: FragmentDto[];
  rawContent: string;
  sections: Record<string, ParsedSectionDto>;
};

export type EntryListItemDto = {
  fileName: string;
  filePath: string;
};

export type EntryLoadResultDto = {
  fileName: string;
  filePath: string;
  entry: JournalEntryDto;
};

export type EntrySaveResultDto = {
  filePath: string;
};

export type EntrySearchRequestDto = {
  query?: string;
  section?: string;
  startDate?: string;
  endDate?: string;
  tags?: string[];
  types?: string[];
  checked?: string[];
  unchecked?: string[];
};

export type EntrySearchResultDto = {
  fileName: string;
  entry: JournalEntryDto;
};

type ParsedSectionDtoRaw = {
  title?: string;
  content?: string[];
  checkboxes?: Record<string, boolean>;
  Title?: string;
  Content?: string[];
  Checkboxes?: Record<string, boolean>;
};

type JournalEntryDtoRaw = {
  date?: string;
  fragments?: FragmentDtoRaw[];
  rawContent?: string;
  sections?: Record<string, ParsedSectionDtoRaw>;
  Date?: string;
  Fragments?: FragmentDtoRaw[];
  RawContent?: string;
  Sections?: Record<string, ParsedSectionDtoRaw>;
};

type EntryListItemDtoRaw = {
  fileName?: string;
  filePath?: string;
  FileName?: string;
  FilePath?: string;
};

type EntryLoadResultDtoRaw = {
  fileName?: string;
  filePath?: string;
  entry?: JournalEntryDtoRaw;
  date?: string;
  rawContent?: string;
  FileName?: string;
  FilePath?: string;
  Entry?: JournalEntryDtoRaw;
  Date?: string;
  RawContent?: string;
};

type EntrySaveResultDtoRaw = {
  filePath?: string;
  FilePath?: string;
};

type EntrySearchResultDtoRaw = {
  fileName?: string;
  entry?: JournalEntryDtoRaw;
  date?: string;
  rawContent?: string;
  FileName?: string;
  Entry?: JournalEntryDtoRaw;
  Date?: string;
  RawContent?: string;
};

function normalizeSection(raw: ParsedSectionDtoRaw): ParsedSectionDto {
  return {
    title: pickCase(raw, "title", "Title", ""),
    content: pickCase(raw, "content", "Content", [] as string[]),
    checkboxes: pickCase(
      raw,
      "checkboxes",
      "Checkboxes",
      {} as Record<string, boolean>,
    ),
  };
}

function normalizeJournalEntry(
  raw: JournalEntryDtoRaw | undefined,
): JournalEntryDto {
  const fragments = pickCase(
    raw,
    "fragments",
    "Fragments",
    [] as FragmentDtoRaw[],
  );
  const sections = pickCase(
    raw,
    "sections",
    "Sections",
    {} as Record<string, ParsedSectionDtoRaw>,
  );
  return {
    date: pickCase(raw, "date", "Date", ""),
    fragments: fragments.map(normalizeFragment),
    rawContent: pickCase(raw, "rawContent", "RawContent", ""),
    sections: Object.fromEntries(
      Object.entries(sections).map(([key, value]) => [
        key,
        normalizeSection(value),
      ]),
    ),
  };
}

function normalizeEntryListItem(raw: EntryListItemDtoRaw): EntryListItemDto {
  return {
    fileName: pickCase(raw, "fileName", "FileName", ""),
    filePath: pickCase(raw, "filePath", "FilePath", ""),
  };
}

function normalizeEntryLoadResult(
  raw: EntryLoadResultDtoRaw,
): EntryLoadResultDto {
  const nestedEntry = pickCase(
    raw,
    "entry",
    "Entry",
    undefined as JournalEntryDtoRaw | undefined,
  );
  const entry = nestedEntry
    ? normalizeJournalEntry(nestedEntry)
    : normalizeJournalEntry({
        date: pickCase(raw, "date", "Date", undefined as string | undefined),
        rawContent: pickCase(
          raw,
          "rawContent",
          "RawContent",
          undefined as string | undefined,
        ),
        fragments: [],
        sections: {},
      });

  return {
    fileName: pickCase(raw, "fileName", "FileName", ""),
    filePath: pickCase(raw, "filePath", "FilePath", ""),
    entry,
  };
}

function normalizeEntrySearchResult(
  raw: EntrySearchResultDtoRaw,
): EntrySearchResultDto {
  const nestedEntry = pickCase(
    raw,
    "entry",
    "Entry",
    undefined as JournalEntryDtoRaw | undefined,
  );
  const entry = nestedEntry
    ? normalizeJournalEntry(nestedEntry)
    : normalizeJournalEntry({
        date: pickCase(raw, "date", "Date", undefined as string | undefined),
        rawContent: pickCase(
          raw,
          "rawContent",
          "RawContent",
          undefined as string | undefined,
        ),
        fragments: [],
        sections: {},
      });

  return {
    fileName: pickCase(raw, "fileName", "FileName", ""),
    entry,
  };
}

export async function listEntries(): Promise<EntryListItemDto[]> {
  const data = await sendCommand<EntryListItemDtoRaw[]>({
    action: "entries.list",
    payload: {},
  });

  return data
    .map(normalizeEntryListItem)
    .filter((item) => Boolean(item.filePath));
}

export async function loadEntry(filePath: string): Promise<EntryLoadResultDto> {
  const data = await sendCommand<EntryLoadResultDtoRaw>({
    action: "entries.load",
    payload: { filePath },
  });

  return normalizeEntryLoadResult(data);
}

export async function saveEntry(payload: {
  content: string;
  filePath?: string;
  mode?: string;
  fileName?: string;
}): Promise<EntrySaveResultDto> {
  const data = await sendCommand<EntrySaveResultDtoRaw>({
    action: "entries.save",
    payload,
  });

  return {
    filePath: pickCase(data, "filePath", "FilePath", ""),
  };
}

export async function deleteEntry(filePath: string): Promise<boolean> {
  return sendCommand<boolean>({
    action: "entries.delete",
    payload: { filePath },
  });
}

export async function searchEntries(
  payload: EntrySearchRequestDto,
): Promise<EntrySearchResultDto[]> {
  const data = await sendCommand<EntrySearchResultDtoRaw[]>({
    action: "search.entries",
    payload,
  });

  return data
    .map(normalizeEntrySearchResult)
    .filter((item) => Boolean(item.fileName));
}
