import { ref } from "vue";
import { defineStore } from "pinia";
import * as api from "@/api/rulebooks";
import type { Rulebook, RulebookTopic, Rule, RuleHeader } from "@/api/rulebooks";
import { useToastStore } from "@/stores/toast";

export const useRulebooksStore = defineStore("rulebooks", () => {
  const rulebooks = ref<Rulebook[]>([]);
  const topicsByRulebook = ref<Record<number, RulebookTopic[]>>({});
  const rulesByTopic = ref<Record<number, RuleHeader[]>>({});
  const currentRule = ref<Rule | null>(null);
  const loading = ref(false);

  async function fetchRulebooks() {
    loading.value = true;
    try {
      rulebooks.value = await api.listRulebooks();
    } catch (e) {
      useToastStore().show("Failed to load rulebooks", "error");
      throw e;
    } finally {
      loading.value = false;
    }
  }

  async function fetchTopics(rulebookId: number) {
    try {
      topicsByRulebook.value[rulebookId] = await api.listTopics(rulebookId);
    } catch (e) {
      useToastStore().show("Failed to load topics", "error");
      throw e;
    }
  }

  async function fetchRules(topicId: number) {
    try {
      const rules = await api.listRules({ topic_id: topicId });
      rulesByTopic.value[topicId] = rules.map(toHeader);
    } catch (e) {
      useToastStore().show("Failed to load rules", "error");
      throw e;
    }
  }

  async function fetchRule(id: number) {
    currentRule.value = await api.getRule(id);
  }

  async function createRulebook(data: { title: string; description?: string }) {
    const rb = await api.createRulebook(data);
    rulebooks.value.push(rb);
    return rb;
  }

  async function updateRulebook(id: number, data: Partial<Pick<Rulebook, "title" | "description" | "always_on">>) {
    const rb = await api.updateRulebook(id, data);
    const idx = rulebooks.value.findIndex((r) => r.id === id);
    if (idx >= 0) rulebooks.value[idx] = rb;
    return rb;
  }

  async function toggleAlwaysOn(id: number) {
    const current = rulebooks.value.find((r) => r.id === id);
    if (!current) return;
    return updateRulebook(id, { always_on: !current.always_on });
  }

  async function deleteRulebook(id: number) {
    await api.deleteRulebook(id);
    rulebooks.value = rulebooks.value.filter((r) => r.id !== id);
    delete topicsByRulebook.value[id];
  }

  async function createTopic(rulebookId: number, data: { title: string; description?: string }) {
    const topic = await api.createTopic(rulebookId, data);
    if (!topicsByRulebook.value[rulebookId]) topicsByRulebook.value[rulebookId] = [];
    topicsByRulebook.value[rulebookId].push(topic);
    return topic;
  }

  async function updateTopic(id: number, data: Partial<Pick<RulebookTopic, "title" | "description" | "order_index">>) {
    const topic = await api.updateTopic(id, data);
    for (const rbId of Object.keys(topicsByRulebook.value)) {
      const list = topicsByRulebook.value[Number(rbId)];
      const idx = list.findIndex((t) => t.id === id);
      if (idx >= 0) list[idx] = topic;
    }
    return topic;
  }

  async function deleteTopic(id: number) {
    await api.deleteTopic(id);
    for (const rbId of Object.keys(topicsByRulebook.value)) {
      topicsByRulebook.value[Number(rbId)] = topicsByRulebook.value[Number(rbId)].filter((t) => t.id !== id);
    }
    delete rulesByTopic.value[id];
  }

  /**
   * A list row built from a full rule. The row shape is the server's
   * rule_brief, so every field it carries has to be mirrored here or the two
   * disagree the moment a list is patched locally instead of re-fetched.
   */
  function toHeader(rule: Rule): api.RuleHeader {
    return {
      id: rule.id,
      title: rule.title,
      statement: rule.statement,
      topic_id: rule.topic_id,
      tier: rule.tier,
      updated_at: rule.updated_at,
      when_to_apply: rule.when_to_apply || undefined,
      arose_from_id: rule.arose_from_id ?? undefined,
    };
  }

  async function createRule(topicId: number, data: Partial<api.RuleWrite> & { title: string; statement: string }) {
    const rule = await api.createRule(topicId, data);
    if (!rulesByTopic.value[topicId]) rulesByTopic.value[topicId] = [];
    rulesByTopic.value[topicId].push(toHeader(rule));
    return rule;
  }

  async function updateRule(id: number, data: Partial<api.RuleWrite>) {
    const rule = await api.updateRule(id, data);
    if (currentRule.value?.id === id) currentRule.value = rule;
    for (const tid of Object.keys(rulesByTopic.value)) {
      const list = rulesByTopic.value[Number(tid)];
      const idx = list.findIndex((r) => r.id === id);
      if (idx >= 0) list[idx] = toHeader(rule);
    }
    return rule;
  }

  async function relateRules(
    fromRuleId: number,
    data: { to_rule_id: number; kind: api.RuleRelationKind; note?: string },
  ) {
    await api.relateRules(fromRuleId, data);
    // Re-read rather than patching locally: the edge reads from BOTH ends, so
    // the far rule's relations changed too and a local splice would show only
    // half of what just happened.
    await fetchRule(fromRuleId);
  }

  async function unrelateRules(relationId: number, refreshRuleId: number) {
    await api.unrelateRules(relationId);
    await fetchRule(refreshRuleId);
  }

  async function deleteRule(id: number) {
    await api.deleteRule(id);
    if (currentRule.value?.id === id) currentRule.value = null;
    for (const tid of Object.keys(rulesByTopic.value)) {
      rulesByTopic.value[Number(tid)] = rulesByTopic.value[Number(tid)].filter((r) => r.id !== id);
    }
  }

  return {
    rulebooks, topicsByRulebook, rulesByTopic, currentRule, loading,
    fetchRulebooks, fetchTopics, fetchRules, fetchRule,
    createRulebook, updateRulebook, toggleAlwaysOn, deleteRulebook,
    createTopic, updateTopic, deleteTopic,
    createRule, updateRule, deleteRule, relateRules, unrelateRules,
  };
});
