import { h as hashAttachment, a as hashKey, c as createReducer, i as isBaseAction, b as createDocument, s as saveToFile, l as loadFromFile, r as readFile, J as JSZip, d as loadFromInput, e as createZip, f as createAction, B as BaseDocument, g as applyMixins, j as BaseActions } from './object-36330a1a.js';
import 'json-stringify-deterministic';
import 'document-model-graphql/document';
import 'immer';
import 'crypto';
import 'fs';
import 'https';
import 'path';
import { z } from 'document-model-graphql/budget-statement';

const ADD_ACCOUNT = "ADD_ACCOUNT";
const UPDATE_ACCOUNT = "UPDATE_ACCOUNT";
const DELETE_ACCOUNT = "DELETE_ACCOUNT";

const ADD_AUDIT_REPORT = "ADD_AUDIT_REPORT";
const DELETE_AUDIT_REPORT = "DELETE_AUDIT_REPORT";
function isAuditReport(audit) {
  return typeof audit.report === "string" && audit.report.startsWith("attachment://");
}

const ADD_COMMENT = "ADD_COMMENT";
const UPDATE_COMMENT = "UPDATE_COMMENT";
const DELETE_COMMENT = "DELETE_COMMENT";

const INIT = "INIT";

const ADD_LINE_ITEM = "ADD_LINE_ITEM";
const UPDATE_LINE_ITEM = "UPDATE_LINE_ITEM";
const DELETE_LINE_ITEM = "DELETE_LINE_ITEM";

const ADD_VESTING = "ADD_VESTING";
const UPDATE_VESTING = "UPDATE_VESTING";
const DELETE_VESTING = "DELETE_VESTING";

const addAccountOperation = (state, action) => {
  return {
    ...state,
    data: {
      ...state.data,
      accounts: [
        ...state.data.accounts ?? [],
        ...action.input.accounts.map(createAccount)
      ]
    }
  };
};
const updateAccountOperation = (state, action) => {
  return {
    ...state,
    data: {
      ...state.data,
      accounts: state.data.accounts.map((account) => {
        const accountUpdate = action.input.accounts.find(
          (a) => a.address === account.address
        );
        return {
          ...account,
          lineItems: accountUpdate?.lineItems ?? account.lineItems,
          name: accountUpdate?.name ?? account.name
        };
      })
    }
  };
};
const deleteAccountOperation = (state, action) => {
  return {
    ...state,
    data: {
      ...state.data,
      accounts: state.data.accounts.filter(
        (account) => !action.input.accounts.find(
          (address) => address === account.address
        )
      )
    }
  };
};

const addAuditReportOperation = (state, action) => {
  const operation = state.operations[state.operations.length - 1];
  action.input.reports.forEach((audit, index) => {
    if (isAuditReport(audit)) {
      state.data.auditReports.push(audit);
    } else {
      const hash = hashAttachment(audit.report.data);
      const attachmentKey = `attachment://audits/${hash}`;
      state.fileRegistry[attachmentKey] = { ...audit.report };
      state.data.auditReports.push({
        timestamp: audit.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
        status: audit.status,
        report: attachmentKey
      });
      operation.input.reports[index].report = attachmentKey;
    }
  });
};
const deleteAuditReportOperation = (state, action) => {
  action.input.reports.forEach((report) => {
    const index = state.data.auditReports.findIndex(
      (audit) => audit.report === report
    );
    if (index > -1) {
      state.data.auditReports.splice(index, 1);
    }
  });
};

const initOperation = (state, action) => {
  return createBudgetStatement({
    ...state,
    ...action.input,
    name: action.input.name ?? state.name,
    revision: action.input.revision ?? state.revision,
    documentType: action.input.documentType ?? state.documentType,
    created: action.input.created ?? state.created,
    lastModified: action.input.lastModified ?? state.lastModified,
    data: {
      owner: {
        ref: action.input.data.owner?.ref || null,
        id: action.input.data.owner?.id || null,
        title: action.input.data.owner?.title || null
      },
      month: action.input.data.month ?? null,
      quoteCurrency: action.input.data.quoteCurrency ?? null,
      vesting: action.input.data.vesting ?? [],
      ftes: action.input.data.ftes ?? null,
      accounts: action.input.data.accounts ?? [],
      auditReports: action.input.data.auditReports ?? [],
      comments: action.input.data.comments ?? []
    }
  });
};

function isEqual(lineItemInput, lineItem) {
  return lineItemInput.category === lineItem.category?.id && lineItemInput.group === lineItem.group?.id;
}
const addLineItemOperation = (state, action) => {
  const newAccounts = state.data.accounts.slice();
  const accountIndex = newAccounts.findIndex(
    (a) => a.address === action.input.account
  );
  if (accountIndex === -1) {
    throw new Error(
      `Account with address ${action.input.account} not found`
    );
  }
  const newAccount = Object.assign({}, newAccounts[accountIndex]);
  newAccount.lineItems = [
    ...newAccount.lineItems,
    ...action.input.lineItems.map(createLineItem)
  ];
  newAccounts[accountIndex] = newAccount;
  return {
    ...state,
    data: {
      ...state.data,
      accounts: newAccounts
    }
  };
};
const updateLineItemOperation = (state, action) => {
  const newAccounts = state.data.accounts.slice();
  const accountIndex = newAccounts.findIndex(
    (a) => a.address === action.input.account
  );
  if (accountIndex === -1) {
    throw new Error(
      `Account with address ${action.input.account} not found`
    );
  }
  const newAccount = Object.assign({}, newAccounts[accountIndex]);
  newAccount.lineItems = newAccount.lineItems.map((lineItem) => {
    const input = action.input.lineItems.find((l) => isEqual(l, lineItem));
    if (!input) {
      return lineItem;
    } else {
      return {
        ...lineItem,
        ...input,
        category: lineItem.category,
        headcountExpense: input.headcountExpense ?? false,
        group: lineItem.group,
        forecast: [
          // replace old forecasts with new forecasts
          ...input.forecast ?? [],
          ...lineItem.forecast.filter(
            (oldForecast) => !input.forecast?.find(
              (newForecast) => newForecast.month === oldForecast.month
            )
          )
        ].filter((forecast) => forecast.value !== null).sort((f1, f2) => f1.month.localeCompare(f2.month))
      };
    }
  });
  newAccounts[accountIndex] = newAccount;
  return {
    ...state,
    data: {
      ...state.data,
      accounts: newAccounts
    }
  };
};
const deleteLineItemOperation = (state, action) => {
  const newAccounts = state.data.accounts.slice();
  const accountIndex = newAccounts.findIndex(
    (a) => a.address === action.input.account
  );
  if (accountIndex === -1) {
    throw new Error(
      `Account with address ${action.input.account} not found`
    );
  }
  const newAccount = Object.assign({}, newAccounts[accountIndex]);
  newAccount.lineItems = newAccount.lineItems.filter(
    (lineItem) => !action.input.lineItems.find((l) => isEqual(l, lineItem))
  );
  newAccounts[accountIndex] = newAccount;
  return {
    ...state,
    data: {
      ...state.data,
      accounts: newAccounts
    }
  };
};

const sortComment = (a, b) => a.timestamp < b.timestamp ? -1 : 1;
const addCommentOperation = (state, action) => {
  state.data.comments.push(
    ...action.input.comments.map((input) => ({
      key: input.key ?? hashKey(),
      author: {
        ref: null,
        id: null,
        username: null,
        roleLabel: null,
        ...input.author
      },
      comment: input.comment || "",
      timestamp: input.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
      status: input.status ?? "Draft"
    }))
  );
  state.data.comments.sort(sortComment);
};
const updateCommentOperation = (state, action) => {
  action.input.comments.forEach((input) => {
    const index = state.data.comments.findIndex((c) => c.key === input.key);
    if (index === -1) {
      return;
    }
    const comment = state.data.comments[index];
    state.data.comments[index] = {
      ...comment,
      ...input,
      author: {
        id: input.author?.id ?? comment.author.id,
        ref: input.author?.ref ?? comment.author.ref,
        roleLabel: input.author?.roleLabel ?? comment.author.roleLabel,
        username: input.author?.username ?? comment.author.username
      },
      comment: input.comment ?? comment.comment,
      timestamp: input.timestamp ?? comment.timestamp
    };
  });
  state.data.comments.sort(sortComment);
};
const deleteCommentOperation = (state, action) => {
  state.data.comments = state.data.comments.filter(
    (comment) => !action.input.comments.includes(comment.key)
  );
};

const sortVesting = (a, b) => a.date < b.date ? -1 : 1;
const addVestingOperation = (state, action) => {
  state.data.vesting.push(
    ...action.input.vesting.map((input) => ({
      ...input,
      key: input.key ?? hashKey(),
      date: input.date ?? "",
      amount: input.amount ?? "",
      amountOld: input.amountOld ?? input.amount ?? "",
      comment: input.comment ?? "",
      currency: input.currency ?? "",
      vested: input.vested ?? false
    }))
  );
  state.data.vesting.sort(sortVesting);
};
const updateVestingOperation = (state, action) => {
  action.input.vesting.forEach((input) => {
    const index = state.data.vesting.findIndex((v) => v.key === input.key);
    if (index === -1) {
      return;
    }
    const vesting = state.data.vesting[index];
    state.data.vesting[index] = {
      ...vesting,
      ...input,
      amount: input.amount ?? vesting.amount,
      amountOld: input.amountOld ?? vesting.amountOld,
      comment: input.comment ?? vesting.comment,
      currency: input.currency ?? vesting.currency,
      date: input.date ?? vesting.date,
      vested: input.vested ?? vesting.vested
    };
  });
  state.data.vesting.sort(sortVesting);
};
const deleteVestingOperation = (state, action) => {
  state.data.vesting = state.data.vesting.filter(
    (vesting) => !action.input.vesting.includes(vesting.key)
  );
};

const reducer = createReducer(
  (state, action) => {
    if (isBaseAction(action)) {
      return state;
    }
    z.BudgetStatementActionSchema().parse(action);
    switch (action.type) {
      case INIT:
        return initOperation(state, action);
      case ADD_ACCOUNT:
        return addAccountOperation(state, action);
      case UPDATE_ACCOUNT:
        return updateAccountOperation(state, action);
      case DELETE_ACCOUNT:
        return deleteAccountOperation(state, action);
      case ADD_LINE_ITEM:
        return addLineItemOperation(state, action);
      case UPDATE_LINE_ITEM:
        return updateLineItemOperation(state, action);
      case DELETE_LINE_ITEM:
        return deleteLineItemOperation(state, action);
      case ADD_AUDIT_REPORT:
        return addAuditReportOperation(state, action);
      case DELETE_AUDIT_REPORT:
        return deleteAuditReportOperation(state, action);
      case ADD_VESTING:
        return addVestingOperation(state, action);
      case UPDATE_VESTING:
        return updateVestingOperation(state, action);
      case DELETE_VESTING:
        return deleteVestingOperation(state, action);
      case ADD_COMMENT:
        return addCommentOperation(state, action);
      case UPDATE_COMMENT:
        return updateCommentOperation(state, action);
      case DELETE_COMMENT:
        return deleteCommentOperation(state, action);
      default:
        return state;
    }
  }
);

const createBudgetStatement = (initialState) => createDocument({
  documentType: "powerhouse/budget-statement",
  ...initialState,
  data: {
    owner: {
      ref: null,
      id: null,
      title: null
    },
    month: null,
    quoteCurrency: null,
    vesting: [],
    ftes: null,
    accounts: [],
    auditReports: [],
    comments: [],
    ...initialState?.data
  }
});
const createAccount = (input) => ({
  ...input,
  name: input.name ?? "",
  lineItems: input.lineItems?.map(createLineItem) ?? new Array()
});
const createLineItem = (input) => ({
  budgetCap: null,
  payment: null,
  actual: null,
  comment: null,
  ...input,
  forecast: input.forecast ?? new Array(),
  headcountExpense: input.headcountExpense ?? false,
  group: input.group ?? null,
  category: input.category ?? null
});
const saveBudgetStatementToFile = (document, path, name) => {
  return saveToFile(document, path, "phbs", name);
};
const loadBudgetStatementFromFile = async (path) => {
  const state = await loadFromFile(
    path,
    reducer
  );
  const auditReports = state.data.auditReports;
  if (!auditReports.length) {
    return state;
  }
  const file = readFile(path);
  const zip = new JSZip();
  await zip.loadAsync(file);
  const fileRegistry = { ...state.fileRegistry };
  await Promise.all(
    auditReports.map(async (audit) => {
      const path2 = audit.report.slice("attachment://".length);
      const file2 = await zip.file(path2);
      if (!file2) {
        throw new Error(`Attachment ${audit.report} not found`);
      }
      const data = await file2.async("base64");
      const { mimeType, extension, fileName } = JSON.parse(file2.comment);
      fileRegistry[audit.report] = {
        data,
        mimeType,
        extension,
        fileName
      };
    })
  );
  return { ...state, fileRegistry };
};
const loadBudgetStatementFromInput = async (input) => {
  const state = await loadFromInput(
    input,
    reducer
  );
  const auditReports = state.data.auditReports;
  if (!auditReports.length) {
    return state;
  }
  const zip = new JSZip();
  await zip.loadAsync(input);
  const fileRegistry = { ...state.fileRegistry };
  await Promise.all(
    auditReports.map(async (audit) => {
      const path = audit.report.slice("attachment://".length);
      const file = await zip.file(path);
      if (!file) {
        throw new Error(`Attachment ${audit.report} not found`);
      }
      const data = await file.async("base64");
      const { mimeType, extension, fileName } = JSON.parse(file.comment);
      fileRegistry[audit.report] = {
        data,
        mimeType,
        extension,
        fileName
      };
    })
  );
  return { ...state, fileRegistry };
};
const saveBudgetStatementToFileHandle = async (document, input) => {
  const zip = await createZip(document);
  const blob = await zip.generateAsync({ type: "blob" });
  const writable = await input.createWritable();
  await writable.write(blob);
  await writable.close();
};

var utils = /*#__PURE__*/Object.freeze({
    __proto__: null,
    createAccount: createAccount,
    createBudgetStatement: createBudgetStatement,
    createLineItem: createLineItem,
    loadBudgetStatementFromFile: loadBudgetStatementFromFile,
    loadBudgetStatementFromInput: loadBudgetStatementFromInput,
    saveBudgetStatementToFile: saveBudgetStatementToFile,
    saveBudgetStatementToFileHandle: saveBudgetStatementToFileHandle
});

const addAccount = (accounts) => createAction(ADD_ACCOUNT, {
  accounts: accounts.map(createAccount)
});
const updateAccount = (accounts) => createAction(UPDATE_ACCOUNT, { accounts });
const deleteAccount = (accounts) => createAction(DELETE_ACCOUNT, { accounts });

const addAuditReport = (reports) => {
  const newTimestamp = /* @__PURE__ */ new Date();
  const newReports = reports.map(({ timestamp, report, status }) => ({
    report,
    timestamp: timestamp ?? newTimestamp.toISOString(),
    status
  }));
  return createAction(ADD_AUDIT_REPORT, {
    reports: newReports
  });
};
const deleteAuditReport = (reports) => createAction(DELETE_AUDIT_REPORT, { reports });

const addComment = (comments) => createAction(ADD_COMMENT, { comments });
const updateComment = (comments) => createAction(UPDATE_COMMENT, { comments });
const deleteComment = (comments) => createAction(DELETE_COMMENT, { comments });

const init = (budgetStatement) => createAction(INIT, budgetStatement);

const addLineItem = (account, lineItems) => createAction(ADD_LINE_ITEM, {
  account,
  lineItems: lineItems.map(createLineItem)
});
const updateLineItem = (account, lineItems) => createAction(UPDATE_LINE_ITEM, {
  account,
  lineItems
});
const deleteLineItem = (account, lineItems) => createAction(DELETE_LINE_ITEM, {
  account,
  lineItems
});

const addVesting = (vesting) => createAction(ADD_VESTING, { vesting });
const updateVesting = (vesting) => createAction(UPDATE_VESTING, { vesting });
const deleteVesting = (vesting) => createAction(DELETE_VESTING, { vesting });

class AccountObject extends BaseDocument {
  /**
   * Adds one or more accounts to the budget statement.
   * @param accounts An array of AccountInput objects to add.
   *
   * @group Account
   */
  addAccount(accounts) {
    return this.dispatch(addAccount(accounts));
  }
  /**
   * Updates one or more existing accounts in the budget statement.
   * @param accounts An array of AccountInput objects to update.
   *
   * @group Account
   */
  updateAccount(accounts) {
    return this.dispatch(updateAccount(accounts));
  }
  /**
   * Deletes one or more accounts from the budget statement.
   * @param addresses An array of addresses of the accounts to delete.
   *
   * @group Account
   */
  deleteAccount(accounts) {
    return this.dispatch(deleteAccount(accounts));
  }
  /**
   * Returns an array of all accounts in the budget statement.
   *
   * @group Account
   */
  get accounts() {
    return this.state.data.accounts;
  }
  /**
   * Returns the Account object with the specified address.
   * @param address The address of the Account to retrieve.
   *
   * @group Account
   */
  getAccount(address) {
    return this.accounts.find((account) => account.address === address);
  }
}

class AuditObject extends BaseDocument {
  /**
   * Adds audit reports to the budget statement.
   * @param reports - An array of audit report objects to add.
   *
   * @group Audit
   */
  addAuditReport(reports) {
    return this.dispatch(addAuditReport(reports));
  }
  /**
   * Deletes audit reports from the budget statement.
   * @param reports - An array of objects that contain the report attachment name of the audits items to be deleted.
   *
   * @group Audit
   */
  deleteAuditReport(reports) {
    return this.dispatch(deleteAuditReport(reports));
  }
  /**
   * Retrieves all audit reports from the budget statement.
   * @returns An array of audit report objects.
   *
   * @group Audit
   */
  getAuditReports() {
    return this.state.data.auditReports;
  }
  /**
   * Retrieves a specific audit report from the budget statement.
   * @param report - The name of the attachment of the report to be retrieved.
   * @returns The audit report object if it exists, or undefined if not.
   *
   * @group Audit
   */
  getAuditReport(report) {
    return this.getAuditReports().find((audit) => audit.report === report);
  }
}

class CommentObject extends BaseDocument {
  addComment(comments) {
    return this.dispatch(addComment(comments));
  }
  updateComment(comments) {
    return this.dispatch(updateComment(comments));
  }
  deleteComment(comments) {
    return this.dispatch(deleteComment(comments));
  }
}

class InitObject extends BaseDocument {
  /**
   * Initializes the state of the budget statement with the provided partial object.
   *
   * @param budgetStatement - A partial object of the budget statement to initialize.
   */
  init(budgetStatement) {
    return this.dispatch(init(budgetStatement));
  }
}

class LineItemObject extends BaseDocument {
  /**
   * Adds a line item to the specified account.
   * @param account The address of the account to which the line item will be added.
   * @param lineItems An array of line item objects to be added to the account.
   *
   * @group Line Item
   */
  addLineItem(account, lineItems) {
    return this.dispatch(addLineItem(account, lineItems));
  }
  /**
   * Updates line items for the specified account.
   * @param account The address of the account for which line items will be updated.
   * @param lineItems An array of line item input objects to be updated.
   *
   * @group Line Item
   */
  updateLineItem(account, lineItems) {
    return this.dispatch(updateLineItem(account, lineItems));
  }
  /**
   * Deletes line items for the specified account.
   * @param account The address of the account for which line items will be deleted.
   * @param lineItems An array of objects that contain the category and group of the line items to be deleted.
   *
   * @group Line Item
   */
  deleteLineItem(account, lineItems) {
    return this.dispatch(deleteLineItem(account, lineItems));
  }
  /**
   * Retrieves line items for the specified account.
   * @param account The address of the account for which line items will be retrieved.
   * @returns An array of line item objects for the specified account, or undefined if the account does not exist.
   *
   * @group Line Item
   */
  getLineItems(account) {
    return this.state.data.accounts.find(
      ({ address }) => address === account
    )?.lineItems;
  }
  /**
   * Retrieves a specific line item for the specified account.
   * @param account The address of the account for which the line item will be retrieved.
   * @param lineItem An object that contains the category and group of the line item to be retrieved.
   * @returns The line item object that matches the specified category and group, or undefined if it does not exist.
   *
   * @group Line Item
   */
  getLineItem(account, lineItem) {
    return this.getLineItems(account)?.find(
      ({ category, group }) => category?.id === lineItem.category && group?.id === lineItem.group
    );
  }
}

class VestingObject extends BaseDocument {
  addVesting(vesting) {
    return this.dispatch(addVesting(vesting));
  }
  updateVesting(vesting) {
    return this.dispatch(updateVesting(vesting));
  }
  deleteVesting(vesting) {
    return this.dispatch(deleteVesting(vesting));
  }
}

let BudgetStatement$2 = class BudgetStatement extends BaseDocument {
  /**
   * The file extension used to save budget statements.
   */
  static fileExtension = "phbs";
  /**
   *
   * Creates a new BudgetStatement instance.
   * @param initialState - An optional object representing the initial state of the BudgetStatement.
   */
  constructor(initialState) {
    super(reducer, createBudgetStatement(initialState));
  }
  /**
   * Gets the month of the budget statement.
   * @category Budget Statement
   */
  get month() {
    return this.state.data.month;
  }
  /**
   * Gets the owner of the budget statement.
   * @category Budget Statement
   */
  get owner() {
    return this.state.data.owner;
  }
  /**
   * Gets the quote currency of the budget statement.
   * @category Budget Statement
   */
  get quoteCurrency() {
    return this.state.data.quoteCurrency;
  }
  /**
   * Saves the budget statement to a file.
   *
   * @param path The path to the file to save.
   * @returns A promise that resolves when the save operation completes.
   */
  saveToFile(path, name) {
    return super.saveToFile(path, BudgetStatement.fileExtension, name);
  }
  /**
   * Loads the budget statement from a file.
   *
   * @param path The path to the file to load.
   * @returns A promise that resolves with the loaded `BudgetStatement` instance.
   */
  loadFromFile(path) {
    return super.loadFromFile(path);
  }
  /**
   * Creates a new `BudgetStatement` instance from a file.
   *
   * @param path The path to the file to load.
   * @returns A promise that resolves with the loaded `BudgetStatement` instance.
   */
  static async fromFile(path) {
    const budgetStatement = new this();
    await budgetStatement.loadFromFile(path);
    return budgetStatement;
  }
};
applyMixins(BudgetStatement$2, [
  AccountObject,
  AuditObject,
  CommentObject,
  InitObject,
  LineItemObject,
  VestingObject
]);

var gen = /*#__PURE__*/Object.freeze({
    __proto__: null,
    BudgetStatement: BudgetStatement$2,
    addAccount: addAccount,
    addAuditReport: addAuditReport,
    addComment: addComment,
    addLineItem: addLineItem,
    addVesting: addVesting,
    deleteAccount: deleteAccount,
    deleteAuditReport: deleteAuditReport,
    deleteComment: deleteComment,
    deleteLineItem: deleteLineItem,
    deleteVesting: deleteVesting,
    init: init,
    updateAccount: updateAccount,
    updateComment: updateComment,
    updateLineItem: updateLineItem,
    updateVesting: updateVesting
});

const { BudgetStatement, ...BudgetActions } = gen;
const actions = { ...BaseActions, ...BudgetActions };

var BudgetStatement$1 = /*#__PURE__*/Object.freeze({
    __proto__: null,
    BudgetStatement: BudgetStatement,
    actions: actions,
    reducer: reducer,
    utils: utils
});

export { BudgetStatement$1 as B, BudgetStatement as a, actions as b, reducer as r, utils as u };
//# sourceMappingURL=index-6dbfd1f9.js.map
