/* eslint-disable function-paren-newline */
/* eslint-disable implicit-arrow-linebreak */
import { selectHighSeasonStart, selectSeasonType } from 'config/default-options';
import { comparer, reaction, toJS } from 'mobx';
import type { ReactionsContext } from 'process/types';
import { shift } from 'radash';
import type { CalculationOptions } from 'stores/calculation/options/types';
import type { Row } from 'stores/tables/payments/types';
import ValidationHelper from 'stores/validation/helper';
import { difference } from 'tools/array';
import { makeDisposable } from 'tools/mobx';
import * as seasonsConstants from './lib/seasons-constants';
import * as seasonsTools from './lib/seasons-tools';
import validatePaymentsTable from './validation';

export default function paymentsReactions({ store }: ReactionsContext) {
  const { $calculation, $tables, $process } = store;

  reaction(
    () => $calculation.element('tbxFirstPaymentPerc').getValue(),
    (firstPaymentPerc) => {
      $tables.payments.setValue(0, firstPaymentPerc);
    }
  );

  reaction(
    () => $calculation.element('tbxLastPaymentPerc').getValue(),
    (lastPaymentPerc) => {
      const paymentsLength = $tables.payments.values.length;
      $tables.payments.setValue(paymentsLength - 1, lastPaymentPerc);
    }
  );

  /**
   * Аннуитет
   */
  reaction(
    () => {
      const graphType = $calculation.element('radioGraphType').getValue();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();

      return {
        graphType,
        leasingPeriod,
      };
    },
    ({ graphType, leasingPeriod }) => {
      if (graphType === 100_000_000) {
        const middlePayments: Row[] = Array.from(
          {
            length: leasingPeriod - 2,
          },
          () => ({
            value: 100,
            status: 'Disabled',
          })
        );

        const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
        const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

        const rows: Row[] = [
          {
            value: firstPaymentPerc,
            status: 'Disabled',
          },
          ...middlePayments,
          {
            value: lastPaymentPerc,
            status: 'Disabled',
          },
        ];

        if (!$process.has('LoadKP')) {
          $tables.payments.setValues(rows.map((row) => row.value));
        }

        $tables.payments.setStatuses(rows.map((row) => row.status));
      }
    },
    {
      fireImmediately: true,
    }
  );

  /**
   * Равноубывающий
   */

  reaction(
    () => {
      const graphType = $calculation.element('radioGraphType').getValue();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
      const parmentsDecreasePercent = $calculation.element('tbxParmentsDecreasePercent').getValue();

      return {
        graphType,
        leasingPeriod,
        parmentsDecreasePercent,
      };
    },
    ({ graphType, leasingPeriod, parmentsDecreasePercent }) => {
      if (graphType === 100_000_002) {
        const middlePayments: Row[] = Array.from(
          {
            length: leasingPeriod - 2,
          },
          (_, k) => {
            const payment = 100 * (parmentsDecreasePercent / 100) ** k;

            return {
              value: Number(payment.toFixed(2)),
              status: 'Disabled',
            };
          }
        );

        const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
        const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

        const rows: Row[] = [
          {
            value: firstPaymentPerc,
            status: 'Disabled',
          },
          ...middlePayments,
          {
            value: lastPaymentPerc,
            status: 'Disabled',
          },
        ];

        if (!$process.has('LoadKP')) {
          $tables.payments.setValues(rows.map((row) => row.value));
        }

        $tables.payments.setStatuses(rows.map((row) => row.status));
      }
    }
  );

  /**
   * Легкий старт
   */

  reaction(
    () => {
      const graphType = $calculation.element('radioGraphType').getValue();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();

      return {
        graphType,
        leasingPeriod,
      };
    },
    ({ graphType, leasingPeriod }) => {
      if (graphType === 100_000_004) {
        const editablePayments: Row[] = [
          {
            value: 25,
            status: 'Default',
          },
          {
            value: 50,
            status: 'Default',
          },
          {
            value: 75,
            status: 'Default',
          },
        ];

        const payments: Row[] = Array.from(
          {
            length: leasingPeriod - 5,
          },
          () => ({
            value: 100,
            status: 'Disabled',
          })
        );

        const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
        const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

        const rows: Row[] = [
          {
            value: firstPaymentPerc,
            status: 'Disabled',
          },
          ...editablePayments,
          ...payments,
          {
            value: lastPaymentPerc,
            status: 'Disabled',
          },
        ];

        if (!$process.has('LoadKP')) {
          $tables.payments.setValues(rows.map((row) => row.value));
        }

        $tables.payments.setStatuses(rows.map((row) => row.status));
      }
    }
  );

  /**
   * Дегрессия
   */
  // TODO: const exeption = tarif?.evo_seasons_type_exception
  const degressionSeasonTypes = new Set([
    100_000_003, 100_000_004, 100_000_005, 100_000_006, 100_000_007,
  ]);
  const seasonSeasonTypes = new Set([100_000_000, 100_000_001, 100_000_002]);

  reaction(
    () => {
      const graphType = $calculation.element('radioGraphType').getValue();

      return graphType;
    },
    (graphType) => {
      let selectSeasonTypeOptions: CalculationOptions['selectSeasonType'] = [];

      if (graphType === 100_000_001) {
        selectSeasonTypeOptions = selectSeasonType.filter((option) =>
          degressionSeasonTypes.has(option.value)
        );
      } else if (graphType === 100_000_003) {
        selectSeasonTypeOptions = selectSeasonType.filter((option) =>
          seasonSeasonTypes.has(option.value)
        );
      }
      $calculation.element('selectSeasonType').setOptions(selectSeasonTypeOptions);
    }
  );

  const degressionSteps: { [key: number]: Array<number> } = {
    100_000_003: [100, 50, 25],
    100_000_004: [100, 30, 10],
    100_000_005: [100, 70, 40],
    100_000_006: [100, 7, 3],
  };

  reaction(
    () => {
      const degressionType = $calculation.element('selectSeasonType').getValue();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
      const graphType = $calculation.element('radioGraphType').getValue();

      return {
        degressionType,
        leasingPeriod,
        graphType,
      };
    },
    ({ degressionType, leasingPeriod, graphType }) => {
      if (graphType === 100_000_001) {
        let middlePayments: Row[] = [];

        switch (degressionType) {
          case 100_000_007: {
            const editablePayments: Row[] = Array.from(
              {
                length: leasingPeriod - 3,
              },
              () => ({
                value: 100,
                status: 'Default',
              })
            );

            middlePayments = [
              {
                value: 100,
                status: 'Disabled',
              },
              ...editablePayments,
            ];

            break;
          }
          case 100_000_003:
          case 100_000_004:
          case 100_000_005:
          case 100_000_006: {
            const [step1, step2, step3] = degressionSteps[degressionType];
            const paymentsInStep = Math.ceil((leasingPeriod - 2) / 3);

            middlePayments = Array.from(
              {
                length: leasingPeriod - 2,
              },
              (_v, i) => {
                let value = step3;

                if (i <= paymentsInStep * 2 - 1) {
                  value = step2;
                }

                if (i <= paymentsInStep - 1) {
                  value = step1;
                }

                return {
                  value,
                  status: 'Disabled',
                };
              }
            );
            break;
          }
          default: {
            break;
          }
        }

        const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
        const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

        const rows: Row[] = [
          {
            value: firstPaymentPerc,
            status: 'Disabled',
          },
          ...middlePayments,
          {
            value: lastPaymentPerc,
            status: 'Disabled',
          },
        ];

        if (!$process.has('LoadKP')) {
          $tables.payments.setValues(rows.map((row) => row.value));
        }

        $tables.payments.setStatuses(rows.map((row) => row.status));
      }
    }
  );

  makeDisposable(
    () =>
      reaction(
        () => {
          const graphType = $calculation.element('radioGraphType').getValue();
          const payments = toJS($tables.payments.values);
          const degressionType = $calculation.element('selectSeasonType').getValue();

          return {
            graphType,
            payments,
            degressionType,
          };
        },
        (nextParams, prevParams) => {
          if (nextParams.graphType === 100_000_001 && nextParams.degressionType === 100_000_007) {
            const changes = difference(nextParams.payments, prevParams.payments);

            if (changes === null || changes.length > 1) return;

            const [changeIndex] = changes;
            const value = nextParams.payments[changeIndex];
            const payments = nextParams.payments.slice(1, -1).map((payment, i) => {
              if (i <= changeIndex - 2) return payment;

              return value;
            });

            const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
            const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

            $tables.payments.setValues([firstPaymentPerc, ...payments, lastPaymentPerc]);
          }
        },
        {
          delay: 50,
          equals: comparer.structural,
        }
      ),
    () => $process.has('LoadKP')
  );

  /**
   * Сезонный
   */

  reaction(
    () => {
      const graphType = $calculation.element('radioGraphType').getValue();

      return {
        graphType,
      };
    },
    ({ graphType }) => {
      if (graphType !== 100_000_003) return;

      const seasonType = $calculation.element('selectSeasonType').getValue();
      const highSeasonStart = $calculation.element('selectHighSeasonStart').getValue();

      if (!seasonType || !highSeasonStart) {
        $tables.payments.setValues([]);
      }
    }
  );

  reaction(
    () => {
      const seasonType = $calculation.element('selectSeasonType').getValue();

      return seasonType;
    },
    (seasonType) => {
      const graphType = $calculation.element('radioGraphType').getValue();
      if (graphType !== 100_000_003) return;

      if (!seasonType) {
        $calculation.element('selectHighSeasonStart').reset();

        return;
      }

      const highSeasonStartOptions = selectHighSeasonStart.filter(
        (option) => !seasonsConstants.FORBIDDEN_HIGH_SEASON_START[seasonType].includes(option.label)
      );

      $calculation.element('selectHighSeasonStart').setOptions(highSeasonStartOptions);
    }
  );

  function generateSeasonPaymentsRows(seasonType: number, shiftNumber: number, payments: number[]) {
    const shiftedPeriods = new Set(
      seasonsConstants.SEASONS_PERIODS[seasonType].map(
        (position) => (position + shiftNumber) % seasonsConstants.SEASONS_PERIOD_NUMBER
      )
    );
    const rows: Row[] = payments.map((value, i) => ({
      value,
      status: shiftedPeriods.has(i) ? 'Default' : 'Disabled',
    }));

    return rows;
  }

  reaction(
    () => {
      const seasonType = $calculation.element('selectSeasonType').getValue();
      const highSeasonStartOption = $calculation.element('selectHighSeasonStart').getOption();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();

      return {
        seasonType,
        highSeasonStartOption,
        leasingPeriod,
      };
    },
    ({ seasonType, highSeasonStartOption, leasingPeriod }) => {
      const graphType = $calculation.element('radioGraphType').getValue();
      if (graphType !== 100_000_003) return;

      if (!seasonType || !highSeasonStartOption) {
        $tables.payments.setValues([]);

        return;
      }

      const seasons = seasonsTools.generateSeasons(
        seasonType,
        seasonsConstants.DEFAULT_SEASONS_VALUES
      );
      const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
      const payments = seasonsTools.generateSeasonsPayments(
        leasingPeriod,
        shift(seasons, shiftNumber)
      );
      const middlePayments: Row[] = generateSeasonPaymentsRows(seasonType, shiftNumber, payments);
      const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
      const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

      const rows: Row[] = [
        {
          value: firstPaymentPerc,
          status: 'Disabled',
        },
        ...middlePayments,
        {
          value: lastPaymentPerc,
          status: 'Disabled',
        },
      ];

      if (!$process.has('LoadKP')) {
        $tables.payments.setValues(rows.map((row) => row.value));
      }

      $tables.payments.setStatuses(rows.map((row) => row.status));
    }
  );

  makeDisposable(
    () =>
      reaction(
        () => {
          const payments = toJS($tables.payments.values);
          const seasons = payments.slice(1, seasonsConstants.SEASONS_PERIOD_NUMBER + 1);

          return seasons;
        },
        (nextSeasons, prevSeasons) => {
          const graphType = $calculation.element('radioGraphType').getValue();
          if (graphType !== 100_000_003) return;

          const seasonType = $calculation.element('selectSeasonType').getValue();
          const highSeasonStartOption = $calculation.element('selectHighSeasonStart').getOption();
          if (!seasonType || !highSeasonStartOption) return;

          const shiftNumber = Number.parseInt(highSeasonStartOption.label, 10) - 2;
          const unshiftedNextSeasons = shift(nextSeasons, -shiftNumber);
          const unshiftedPrevSeasons = shift(prevSeasons, -shiftNumber);

          const changes = difference(unshiftedNextSeasons, unshiftedPrevSeasons);
          if (changes === null || changes.length > 1) return;

          const [changeIndex] = changes;
          const positionIndex = seasonsTools.getPositionIndex(seasonType, changeIndex);

          const values = seasonsTools.getSeasonsValues(seasonType, unshiftedNextSeasons);
          values[positionIndex] = unshiftedNextSeasons[changeIndex];

          const seasons = seasonsTools.generateSeasons(seasonType, values);
          const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();
          const payments = seasonsTools.generateSeasonsPayments(
            leasingPeriod,
            shift(seasons, shiftNumber)
          );
          const rows: Row[] = generateSeasonPaymentsRows(seasonType, shiftNumber, payments);
          const firstPaymentPerc = $calculation.element('tbxFirstPaymentPerc').getValue();
          const lastPaymentPerc = $calculation.element('tbxLastPaymentPerc').getValue();

          $tables.payments.setRows([
            {
              value: firstPaymentPerc,
              status: 'Disabled',
            },
            ...rows,
            {
              value: lastPaymentPerc,
              status: 'Disabled',
            },
          ]);
        },
        {
          delay: 50,
          equals: comparer.structural,
        }
      ),
    () => $process.has('LoadKP')
  );

  /**
   * Валидация
   */
  const validationHelper = new ValidationHelper();

  reaction(
    () => {
      const payments = toJS($tables.payments.values);
      const graphType = $calculation.element('radioGraphType').getValue();
      const seasonType = $calculation.element('selectSeasonType').getValue();
      const highSeasonStart = $calculation.element('selectHighSeasonStart').getValue();
      const leasingPeriod = $calculation.element('tbxLeasingPeriod').getValue();

      return {
        payments,
        graphType,
        seasonType,
        highSeasonStart,
        leasingPeriod,
      };
    },
    () => {
      validationHelper.removeErrors();
      const errorText = validatePaymentsTable(store);

      if (errorText) {
        const removeError = $tables.payments.validation.addError(errorText);
        validationHelper.add(removeError);
      }
    },
    {
      delay: 50,
      equals: comparer.structural,
    }
  );
}
