import type { Plugin } from "../../core/types";
import { waitForElm } from "@/seqta/utils/waitForElm";

interface TimetableEntryData {
  ci: number;
  description: string;
  room: string;
  staff: string;
}

interface TimetableOverrides {
  [ci: string]: { room?: string; staff?: string };
}

interface TimetableOverridesBySubject {
  [description: string]: { room?: string; staff?: string };
}

interface TimetableStorage {
  timetableOverrides?: TimetableOverrides;
  timetableOverridesBySubject?: TimetableOverridesBySubject;
}

/** SEQTA timetable entries use .teacher and .room as direct children, and data-instance for ci */
function getRoomAndTeacherElements(entry: HTMLElement): {
  roomEl: HTMLElement | null;
  teacherEl: HTMLElement | null;
} {
  const roomEl = entry.querySelector(".room") as HTMLElement | null;
  const teacherEl = entry.querySelector(".teacher") as HTMLElement | null;
  return { roomEl, teacherEl };
}

const EDIT_ICON_SVG =
  '<svg width="20" height="20" viewBox="0 0 24 24"><g style="fill: currentcolor;"><path d="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z"/></g></svg>';

function showEditModal(
  item: TimetableEntryData,
  overrides: TimetableOverrides | undefined,
  overridesBySubject: TimetableOverridesBySubject | undefined,
  onSave: (
    ci: number,
    room: string,
    staff: string,
    applyToFuture: boolean,
  ) => void,
  onClear: (ci: number) => void,
): void {
  const overlay = document.createElement("div");
  overlay.className = "timetable-edit-modal-overlay";

  const modal = document.createElement("div");
  modal.className = "timetable-edit-modal";

  const override = overrides?.[String(item.ci)] ?? overridesBySubject?.[item.description];

  const roomValue = override?.room ?? item.room ?? "";
  const staffValue = override?.staff ?? item.staff ?? "";

  const escapeHtml = (s: string) =>
    s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
  const title = escapeHtml(item.description);

  modal.innerHTML = `
    <h3>Edit ${title}</h3>
    <label for="timetable-edit-room">Room</label>
    <input type="text" id="timetable-edit-room" value="${roomValue.replace(/"/g, "&quot;")}" placeholder="Room" />
    <label for="timetable-edit-staff">Teacher</label>
    <input type="text" id="timetable-edit-staff" value="${staffValue.replace(/"/g, "&quot;")}" placeholder="Teacher" />
    <div class="timetable-edit-modal-checkbox">
      <input type="checkbox" id="timetable-edit-apply-future" />
      <label for="timetable-edit-apply-future">Apply to future weeks</label>
    </div>
    <div class="timetable-edit-modal-actions">
      ${override ? '<button type="button" class="timetable-edit-btn-clear">Clear</button>' : ""}
      <button type="button" class="timetable-edit-btn-cancel">Cancel</button>
      <button type="button" class="timetable-edit-btn-save">Save</button>
    </div>
  `;

  overlay.appendChild(modal);

  const removeModal = () => {
    overlay.remove();
    document.removeEventListener("keydown", handleKeydown);
  };

  const handleKeydown = (e: KeyboardEvent) => {
    if (e.key === "Escape") removeModal();
  };

  overlay.addEventListener("click", (e) => {
    if (e.target === overlay) removeModal();
  });

  modal.addEventListener("click", (e) => e.stopPropagation());
  modal.addEventListener("mousedown", (e) => e.stopPropagation());
  modal.addEventListener("mouseup", (e) => e.stopPropagation());

  const roomInput = modal.querySelector("#timetable-edit-room") as HTMLInputElement;
  const staffInput = modal.querySelector("#timetable-edit-staff") as HTMLInputElement;
  const applyFutureCheckbox = modal.querySelector("#timetable-edit-apply-future") as HTMLInputElement;

  modal.querySelector(".timetable-edit-btn-save")?.addEventListener("click", () => {
    onSave(
      item.ci,
      roomInput.value.trim(),
      staffInput.value.trim(),
      applyFutureCheckbox?.checked ?? false,
    );
    removeModal();
  });

  modal.querySelector(".timetable-edit-btn-cancel")?.addEventListener("click", removeModal);

  const clearBtn = modal.querySelector(".timetable-edit-btn-clear");
  if (clearBtn) {
    clearBtn.addEventListener("click", () => {
      onClear(item.ci);
      removeModal();
    });
  }

  document.body.appendChild(overlay);
  document.addEventListener("keydown", handleKeydown);
  roomInput?.focus();
}

const timetableEditPlugin = {
  run: async (api: Parameters<NonNullable<Plugin["run"]>>[0]) => {
    await api.storage.loaded;

    let observer: MutationObserver | null = null;
    let quickbarObserver: MutationObserver | null = null;
    let quickbarSyncTimer: ReturnType<typeof setTimeout> | null = null;
    let lastClickedCi: number | null = null;
    let lastClickedEntry: {
      roomEl: HTMLElement | null;
      teacherEl: HTMLElement | null;
      item: TimetableEntryData;
    } | null = null;
    let lastSyncedQuickbarCi: number | null = null;

    const getOverrides = (): TimetableOverrides =>
      api.storage.timetableOverrides ?? {};
    const getOverridesBySubject = (): TimetableOverridesBySubject =>
      api.storage.timetableOverridesBySubject ?? {};

    const getEffectiveOverride = (
      ci: number,
      description: string,
    ): { room?: string; staff?: string } | undefined =>
      getOverrides()[String(ci)] ?? getOverridesBySubject()[description];

    const findClassEntry = (
      title: string,
      calendarId?: string | null,
    ): HTMLElement | null => {
      if (calendarId) {
        const byCalendar = document.querySelector(
          `.timetablepage .entry.class[data-calendarid="${calendarId}"]`,
        );
        if (byCalendar) return byCalendar as HTMLElement;
      }

      for (const entry of document.querySelectorAll(".timetablepage .entry.class")) {
        const entryTitle = entry.querySelector(".title")?.textContent?.trim();
        if (entryTitle === title) return entry as HTMLElement;
      }
      return null;
    };

    const resolveContextFromQuickbar = (quickbar: HTMLElement): void => {
      const title = quickbar.querySelector(".title")?.textContent?.trim() ?? "";
      if (!title) return;

      const quickbarRoom = quickbar.querySelector(".meta .room")?.textContent?.trim() ?? "";
      const quickbarStaff =
        quickbar.querySelector(".meta .teacher")?.textContent?.trim() ?? "";

      const entry = findClassEntry(title);
      if (entry) {
        const ciStr = entry.getAttribute("data-instance");
        const ci = ciStr ? parseInt(ciStr, 10) : NaN;
        const { roomEl, teacherEl } = getRoomAndTeacherElements(entry);
        const description = title;
        const room = roomEl?.textContent?.trim() ?? quickbarRoom;
        const staff = teacherEl?.textContent?.trim() ?? quickbarStaff;
        lastClickedCi = isNaN(ci) ? null : ci;
        lastClickedEntry = {
          roomEl,
          teacherEl,
          item: { ci: isNaN(ci) ? 0 : ci, description, room, staff },
        };
        lastSyncedQuickbarCi = null;
        return;
      }

      lastClickedCi = null;
      lastClickedEntry = {
        roomEl: null,
        teacherEl: null,
        item: {
          ci: 0,
          description: title,
          room: quickbarRoom,
          staff: quickbarStaff,
        },
      };
      lastSyncedQuickbarCi = null;
    };

    const processEntry = (entry: HTMLElement): void => {
      if (entry.classList.contains("assessment") || entry.hasAttribute("data-timetable-edit-processed")) return;

      const ciStr = entry.getAttribute("data-instance");
      if (!ciStr) return;

      const ci = parseInt(ciStr, 10);
      if (isNaN(ci)) return;

      const { roomEl, teacherEl } = getRoomAndTeacherElements(entry);
      if (!roomEl && !teacherEl) return;

      const titleEl = entry.querySelector(".title");
      const description = titleEl?.textContent?.trim() ?? "";
      const room = roomEl?.textContent?.trim() ?? "";
      const staff = teacherEl?.textContent?.trim() ?? "";

      const item: TimetableEntryData = { ci, description, room, staff };

      entry.setAttribute("data-timetable-edit-processed", "true");

      const override = getEffectiveOverride(ci, description);
      if (override) {
        if (override.room !== undefined && roomEl) roomEl.textContent = override.room;
        if (override.staff !== undefined && teacherEl) teacherEl.textContent = override.staff;
      }

      entry.addEventListener(
        "click",
        () => {
          lastClickedCi = ci;
          lastClickedEntry = { roomEl, teacherEl, item };
          lastSyncedQuickbarCi = null;
          scheduleQuickbarSync();
        },
        true,
      );
    };

    const processAllEntries = () => {
      document.querySelectorAll(".timetablepage .entry.class").forEach((entry) => {
        processEntry(entry as HTMLElement);
      });
    };

    const getVisibleClassQuickbar = (): HTMLElement | null => {
      const quickbar = document.querySelector(".timetablepage .quickbar.visible");
      if (!quickbar || quickbar.getAttribute("data-type") !== "class") return null;
      return quickbar as HTMLElement;
    };

    const applyOverridesToQuickbar = (quickbar: HTMLElement): void => {
      resolveContextFromQuickbar(quickbar);

      const description =
        quickbar.querySelector(".title")?.textContent?.trim() ??
        lastClickedEntry?.item.description ??
        "";
      if (!description) return;

      const ci = lastClickedCi ?? lastClickedEntry?.item.ci ?? 0;
      if (lastSyncedQuickbarCi === ci && lastClickedCi !== null) return;

      const override = getEffectiveOverride(ci, description);
      if (!override) {
        lastSyncedQuickbarCi = ci;
        return;
      }

      const roomEl = quickbar.querySelector(".meta .room");
      const teacherEl = quickbar.querySelector(".meta .teacher");
      if (override.room !== undefined && !roomEl) return;
      if (override.staff !== undefined && !teacherEl) return;

      if (override.room !== undefined && roomEl && roomEl.textContent !== override.room) {
        roomEl.textContent = override.room;
      }
      if (override.staff !== undefined && teacherEl && teacherEl.textContent !== override.staff) {
        teacherEl.textContent = override.staff;
      }

      lastSyncedQuickbarCi = ci;
    };

    const updateVisibleQuickbar = (room: string, staff: string): void => {
      const quickbar = getVisibleClassQuickbar();
      if (!quickbar) return;
      const roomEl = quickbar.querySelector(".meta .room");
      const teacherEl = quickbar.querySelector(".meta .teacher");
      if (roomEl && roomEl.textContent !== room) roomEl.textContent = room;
      if (teacherEl && teacherEl.textContent !== staff) teacherEl.textContent = staff;
      if (lastClickedCi !== null) lastSyncedQuickbarCi = lastClickedCi;
    };

    const syncClassQuickbar = (quickbar: HTMLElement): void => {
      applyOverridesToQuickbar(quickbar);
      addEditButtonToQuickbar(quickbar);
    };

    const scheduleQuickbarSync = (): void => {
      if (quickbarSyncTimer !== null) clearTimeout(quickbarSyncTimer);

      let attempts = 0;
      const maxAttempts = 15;
      const trySync = (): void => {
        const quickbar = getVisibleClassQuickbar();
        if (!quickbar) {
          if (++attempts < maxAttempts) {
            quickbarSyncTimer = setTimeout(trySync, 50);
          }
          return;
        }

        syncClassQuickbar(quickbar);

        const hasButton = quickbar.querySelector(".timetable-edit-quickbar-btn");
        const hasActions = quickbar.querySelector(".actions");
        if ((!hasButton || !hasActions) && ++attempts < maxAttempts) {
          quickbarSyncTimer = setTimeout(trySync, 50);
        }
      };

      requestAnimationFrame(trySync);
    };

    const addEditButtonToQuickbar = (quickbar: HTMLElement) => {
      if (quickbar.querySelector(".timetable-edit-quickbar-btn")) return;

      const actions = quickbar.querySelector(".actions");
      if (!actions) return;

      const colourBtn = actions.querySelector(
        "[title='Choose a colour'], button.uiButton",
      );
      const btn = document.createElement("button");
      btn.type = "button";
      btn.className =
        colourBtn instanceof HTMLElement
          ? `${colourBtn.className} timetable-edit-quickbar-btn`
          : "timetable-edit-quickbar-btn";
      btn.title = "Edit room and teacher";
      btn.innerHTML = EDIT_ICON_SVG;

      btn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();

        const qb = (e.currentTarget as HTMLElement).closest(".quickbar");
        if (!qb) return;

        resolveContextFromQuickbar(qb as HTMLElement);
        const entryData = lastClickedEntry;
        if (!entryData) return;

        const quickbarRoom = qb.querySelector(".meta .room")?.textContent?.trim() ?? "";
        const quickbarTeacher = qb.querySelector(".meta .teacher")?.textContent?.trim() ?? "";
        const quickbarTitle = qb.querySelector(".title")?.textContent?.trim() ?? "";
        const ci = lastClickedCi ?? entryData.item.ci;
        const item: TimetableEntryData = {
          ci,
          description: quickbarTitle || entryData.item.description,
          room: quickbarRoom || entryData.item.room,
          staff: quickbarTeacher || entryData.item.staff,
        };

        showEditModal(
          item,
          getOverrides(),
          getOverridesBySubject(),
          (saveCi, room, staff, applyToFuture) => {
            if (applyToFuture || !saveCi) {
              const bySubject = { ...getOverridesBySubject() };
              bySubject[item.description] = {
                room: room || undefined,
                staff: staff || undefined,
              };
              api.storage.timetableOverridesBySubject = bySubject;
            } else {
              const current = getOverrides();
              api.storage.timetableOverrides = {
                ...current,
                [String(saveCi)]: { room: room || undefined, staff: staff || undefined },
              };
            }
            if (entryData.roomEl) entryData.roomEl.textContent = room;
            if (entryData.teacherEl) entryData.teacherEl.textContent = staff;
            updateVisibleQuickbar(room, staff);
            processAllEntries();
          },
          (clearCi) => {
            if (clearCi) {
              const current = getOverrides();
              delete current[String(clearCi)];
              api.storage.timetableOverrides = current;
            }
            const bySubject = getOverridesBySubject();
            delete bySubject[item.description];
            api.storage.timetableOverridesBySubject = bySubject;
            if (entryData.roomEl) entryData.roomEl.textContent = item.room;
            if (entryData.teacherEl) entryData.teacherEl.textContent = item.staff;
            updateVisibleQuickbar(item.room, item.staff);
            processAllEntries();
          },
        );
      });

      actions.insertBefore(
        btn,
        colourBtn ?? actions.firstChild,
      );
    };

    const syncQuickbarFromDOM = () => {
      const quickbar = getVisibleClassQuickbar();
      if (!quickbar) return;
      syncClassQuickbar(quickbar);
    };

    const setupQuickbarObserver = () => {
      const timetablePage = document.querySelector(".timetablepage");
      if (!timetablePage || quickbarObserver) return;

      quickbarObserver = new MutationObserver((mutations) => {
        const quickbar = getVisibleClassQuickbar();
        if (!quickbar) return;

        const shouldSync = mutations.some((mutation) => {
          if (mutation.type === "attributes" && mutation.attributeName === "class") {
            const target = mutation.target as HTMLElement;
            return target.classList.contains("quickbar");
          }
          if (mutation.type === "childList") {
            const target = mutation.target as HTMLElement;
            return target.classList?.contains("quickbar") || target.closest?.(".quickbar");
          }
          return false;
        });

        if (shouldSync) scheduleQuickbarSync();
      });

      quickbarObserver.observe(timetablePage, {
        subtree: true,
        childList: true,
        attributes: true,
        attributeFilter: ["class"],
      });
    };

    const onTimetableEntryClick = (event: Event): void => {
      const target = event.target as HTMLElement;
      if (!target.closest?.(".timetablepage .entry.class")) return;
      lastSyncedQuickbarCi = null;
      scheduleQuickbarSync();
    };

    document.addEventListener("click", onTimetableEntryClick, true);

    const handleTimetable = async () => {
      // Class entries (`div.entry.class`) load after the page shell; don't fail the whole
      // setup if they are slow or briefly absent (e.g. navigation). Observers still catch them.
      try {
        await waitForElm(".timetablepage .entry.class", true, 50, 300);
      } catch {
        /* entries may appear later */
      }
      processAllEntries();
      setupQuickbarObserver();
      syncQuickbarFromDOM();

      const timetablePage = document.querySelector(".timetablepage");
      if (timetablePage && !observer) {
        observer = new MutationObserver(() => {
          document.querySelectorAll(".timetablepage .entry.class").forEach((entry) => {
            if (!entry.hasAttribute("data-timetable-edit-processed")) {
              processEntry(entry as HTMLElement);
            }
          });
        });
        observer.observe(timetablePage, { childList: true, subtree: true });
      }
    };

    const { unregister } = api.seqta.onMount(".timetablepage", handleTimetable);

    return () => {
      unregister();
      document.removeEventListener("click", onTimetableEntryClick, true);
      observer?.disconnect();
      quickbarObserver?.disconnect();
      if (quickbarSyncTimer !== null) clearTimeout(quickbarSyncTimer);
      document.querySelectorAll("[data-timetable-edit-processed]").forEach((el) => {
        el.removeAttribute("data-timetable-edit-processed");
      });
      document.querySelectorAll(".timetable-edit-quickbar-btn").forEach((el) => el.remove());
    };
  },
};

export default timetableEditPlugin;
