// Shared RAPBS state and accounting-to-budget selectors.
const BudgetContext = React.createContext(null);

function BudgetProvider({ children }) {
  const [academicYears, setAcademicYears] = React.useState(() => RAPBS_ACADEMIC_YEARS.map(academicYear => ({ ...academicYear, approvalHistory: [...academicYear.approvalHistory] })));
  const [yearScopes, setYearScopes] = React.useState(() => RAPBS_YEAR_SCOPES.map(scope => ({ ...scope, categoryIds: [...scope.categoryIds], itemIds: [...scope.itemIds], itemOverrides: { ...scope.itemOverrides } })));
  const [categories, setCategories] = React.useState(() => RAPBS_CATEGORIES.map(category => ({ ...category })));
  const [items, setItems] = React.useState(() => RAPBS_ITEMS.map(item => ({ ...item })));
  const [budgets, setBudgets] = React.useState(() => RAPBS_BUDGETS.map(budget => ({ ...budget })));
  const [journals, setJournals] = React.useState(() => JOURNALS.map(journal => ({ ...journal, lines: journal.lines.map(line => ({ ...line })) })));
  const [selectedAcademicYearId, setSelectedAcademicYearId] = React.useState(null);
  const [filter, setFilter] = React.useState('all');

  const value = {
    academicYears, setAcademicYears,
    yearScopes, setYearScopes,
    categories, setCategories,
    items, setItems,
    budgets, setBudgets,
    journals, setJournals,
    selectedAcademicYearId, setSelectedAcademicYearId,
    filter, setFilter,
  };

  return <BudgetContext.Provider value={value}>{children}</BudgetContext.Provider>;
}

function useBudgetStore() {
  const store = React.useContext(BudgetContext);
  if (!store) throw new Error('useBudgetStore must be used inside BudgetProvider.');
  return store;
}

function academicYearRange(academicYear) {
  return {
    start: `${academicYear.startYear}-07-01`,
    endExclusive: `${academicYear.endYear}-07-01`,
  };
}

function academicYearForPeriod(store, period) {
  const date = `${period}-01`;
  return store.academicYears.find(academicYear => {
    const range = academicYearRange(academicYear);
    return date >= range.start && date < range.endExclusive;
  }) || null;
}

function effectiveRapbsItems(store, academicYearId) {
  const scope = store.yearScopes.find(value => value.academicYearId === academicYearId);
  if (!scope) return [];
  return store.items
    .filter(item => scope.itemIds.includes(item.id))
    .map(item => ({ ...item, ...(scope.itemOverrides[item.id] || {}) }));
}

function effectiveRapbsCategories(store, academicYearId) {
  const scope = store.yearScopes.find(value => value.academicYearId === academicYearId);
  if (!scope) return [];
  return store.categories.filter(category => scope.categoryIds.includes(category.id));
}

function budgetAmountFor(store, academicYear, itemId) {
  return store.budgets.find(budget => budget.year === academicYear.budgetYear && budget.itemId === itemId)?.amount || 0;
}

function budgetTransactionLines(store, journals, academicYearId, { includePending = true } = {}) {
  const academicYear = store.academicYears.find(value => value.id === academicYearId);
  if (!academicYear) return [];
  const range = academicYearRange(academicYear);
  const categories = effectiveRapbsCategories(store, academicYearId);
  const activeItems = effectiveRapbsItems(store, academicYearId).filter(item => item.active);
  const activeItemByCode = Object.fromEntries(activeItems.map(item => [item.coaCode, item]));
  const masterItemByCode = Object.fromEntries(store.items.map(item => [item.coaCode, item]));

  return journals
    .filter(journal => journal.date >= range.start && journal.date < range.endExclusive)
    .filter(journal => includePending || journal.status === 'posted')
    .flatMap(journal => journal.lines.map((line, lineIndex) => {
      if (journal.source === 'budget-operation' && !line.budgetType) return null;
      const account = ACC[line.code];
      const mappedItem = activeItemByCode[line.code] || null;
      const item = mappedItem && (!line.budgetType || line.budgetType === mappedItem.type) ? mappedItem : null;
      const masterItem = masterItemByCode[line.code] || null;
      const type = line.budgetType || item?.type || masterItem?.type || (account?.type === 'pendapatan' ? 'income' : account?.type === 'beban' ? 'outcome' : null);
      if (!type) return null;
      const amount = type === 'income'
        ? (line.credit || 0) - (line.debit || 0)
        : (line.debit || 0) - (line.credit || 0);
      if (!amount) return null;
      const category = item ? categories.find(value => value.id === item.categoryId) || null : null;
      return {
        id: `${journal.id}:${lineIndex}:${line.code}`,
        journalId: journal.id,
        date: journal.date,
        ref: journal.ref,
        desc: journal.desc,
        status: journal.status,
        code: line.code,
        accountName: account?.name || line.code,
        accountType: account?.type || null,
        debit: line.debit || 0,
        credit: line.credit || 0,
        amount,
        type,
        item,
        category,
        mappingStatus: item ? 'mapped' : 'unbudgeted',
        budgetAmount: item ? budgetAmountFor(store, academicYear, item.id) : 0,
      };
    }).filter(Boolean));
}

function rapbsActualComparison(store, journals, academicYearId) {
  const academicYear = store.academicYears.find(value => value.id === academicYearId);
  if (!academicYear) return null;
  const categories = effectiveRapbsCategories(store, academicYearId);
  const items = effectiveRapbsItems(store, academicYearId).filter(item => item.active);
  const lines = budgetTransactionLines(store, journals, academicYearId, { includePending: false });
  const actualByItem = lines.filter(line => line.item).reduce((result, line) => {
    result[line.item.id] = (result[line.item.id] || 0) + line.amount;
    return result;
  }, {});

  const rows = items.map(item => {
    const budget = budgetAmountFor(store, academicYear, item.id);
    const actual = actualByItem[item.id] || 0;
    return {
      ...item,
      category: categories.find(value => value.id === item.categoryId) || null,
      budget,
      actual,
      remaining: budget - actual,
      percentage: budget > 0 ? (actual / budget) * 100 : null,
      lines: lines.filter(line => line.item?.id === item.id),
    };
  });

  const unbudgeted = lines.filter(line => !line.item);
  const summarize = type => {
    const typeRows = rows.filter(row => row.type === type);
    const typeUnbudgeted = unbudgeted.filter(line => line.type === type);
    const budget = typeRows.reduce((sum, row) => sum + row.budget, 0);
    const mappedActual = typeRows.reduce((sum, row) => sum + row.actual, 0);
    const unbudgetedActual = typeUnbudgeted.reduce((sum, line) => sum + line.amount, 0);
    return {
      budget,
      mappedActual,
      unbudgetedActual,
      totalActual: mappedActual + unbudgetedActual,
      remaining: budget - mappedActual,
      percentage: budget > 0 ? (mappedActual / budget) * 100 : null,
      count: typeRows.length,
      unbudgetedCount: typeUnbudgeted.length,
    };
  };

  return {
    academicYear,
    rows,
    lines,
    unbudgeted,
    income: summarize('income'),
    outcome: summarize('outcome'),
  };
}

Object.assign(window, {
  BudgetProvider,
  useBudgetStore,
  academicYearRange,
  academicYearForPeriod,
  effectiveRapbsItems,
  effectiveRapbsCategories,
  budgetAmountFor,
  budgetTransactionLines,
  rapbsActualComparison,
});
