/*
 This file is part of GNU Taler
 (C) 2022-2025 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

import {
  decodeCrockFromURI,
  urlPattern,
  useCurrentLocation,
  useNavigationContext,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";

import { assertUnreachable } from "@gnu-taler/taler-util";
import { useEffect } from "preact/hooks";
import { HandleAccountNotReady } from "./components/HandleAccountNotReady.js";
import { ExchangeAmlFrame } from "./ExchangeAmlFrame.js";
import { useCurrentDecisionRequest } from "./hooks/decision-request.js";
import {
  OfficerReady,
  useExpireSessionAfter1hr,
  useOfficer,
} from "./hooks/officer.js";
import { AccountDetails } from "./pages/AccountDetails.js";
import {
  AccountList,
  HomeIcon,
  PeopleIcon,
  SearchIcon,
  TransfersIcon,
} from "./pages/AccountList.js";
import { Dashboard } from "./pages/Dashboard.js";
import { DecisionWizard, WizardSteps } from "./pages/DecisionWizard.js";
import { Profile } from "./pages/Profile.js";
import { Search } from "./pages/Search.js";
import { ShowCollectedInfo } from "./pages/ShowCollectedInfo.js";
import { Transfers } from "./pages/Transfers.js";

const TALER_SCREEN_ID = 126;

export function Routing(): VNode {
  const session = useOfficer();

  if (session.state === "ready") {
    return (
      <ExchangeAmlFrame officer={session} NavigationBar={Navigation}>
        <PrivateRouting officer={session} />
      </ExchangeAmlFrame>
    );
  }
  return (
    <ExchangeAmlFrame NavigationBar={Navigation}>
      <PublicRounting />
    </ExchangeAmlFrame>
  );
}

const publicPages = {
  config: urlPattern(/\/config/, () => "#/config"),
  login: urlPattern(/\/login/, () => "#/login"),
};

function PublicRounting(): VNode {
  const { i18n } = useTranslationContext();
  const location = useCurrentLocation(publicPages);
  const session = useOfficer();

  switch (location.name) {
    case undefined: {
      if (session.state !== "ready") {
        return <HandleAccountNotReady officer={session} />;
      } else {
        return <div />;
      }
    }
    case "config": {
      return (
        <Fragment>
          <div class="sm:mx-auto sm:w-full sm:max-w-sm">
            <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to exchange config!`}</h2>
          </div>
        </Fragment>
      );
    }
    case "login": {
      return (
        <Fragment>
          <div class="sm:mx-auto sm:w-full sm:max-w-sm">
            <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to exchange config!`}</h2>
          </div>
        </Fragment>
      );
    }
    default:
      assertUnreachable(location);
  }
}

const privatePages = {
  profile: urlPattern(/\/profile/, () => "#/profile"),
  dashboard: urlPattern(/\/dashboard/, () => "#/dashboard"),
  statsDownload: urlPattern(/\/download-stats/, () => "#/download-stats"),
  /**
   * This 4 URL could be only one with multiple params
   */
  decideWithStep: urlPattern<{ cid: string; step: string }>(
    /\/decide\/(?<cid>[a-zA-Z0-9]+)\/(?<step>[a-z]+)/,
    ({ cid, step }) => `#/decide/${cid}/${step}`,
  ),
  decide: urlPattern<{ cid: string }>(
    /\/decide\/(?<cid>[a-zA-Z0-9]+)/,
    ({ cid }) => `#/decide/${cid}`,
  ),
  decideNewWithStep: urlPattern<{ cid: string; payto: string; step: string }>(
    /\/decide_new\/(?<cid>[a-zA-Z0-9]+)\/(?<payto>[a-zA-Z0-9]+)\/(?<step>[a-z]+)/,
    ({ cid, payto, step }) => `#/decide_new/${cid}/${payto}/${step}`,
  ),
  decideNew: urlPattern<{ cid: string; payto: string }>(
    /\/decide_new\/(?<cid>[a-zA-Z0-9]+)\/(?<payto>[a-zA-Z0-9]+)/,
    ({ cid, payto }) => `#/decide_new/${cid}/${payto}`,
  ),
  showCollectedInfo: urlPattern<{ cid: string; rowId: string }>(
    /\/show-collected\/(?<cid>[a-zA-Z0-9]+)\/(?<rowId>[0-9]+)/,
    ({ cid, rowId }) => `#/show-collected/${cid}/${rowId}`,
  ),
  search: urlPattern(/\/search/, () => "#/search"),
  transfersForAccount: urlPattern<{ cid: string }>(
    /\/transfers\/(?<cid>[a-zA-Z0-9]+)/,
    ({ cid }) => `#/transfers/${cid}`,
  ),
  transfers: urlPattern(/\/transfers/, () => "#/transfers"),
  accounts: urlPattern(/\/accounts/, () => "#/accounts"),
  account: urlPattern<{ cid: string }>(
    /\/account\/(?<cid>[a-zA-Z0-9]+)/,
    ({ cid }) => `#/account/${cid}`,
  ),
};

function PrivateRouting({ officer }: { officer: OfficerReady }): VNode {
  const { navigateTo } = useNavigationContext();
  const location = useCurrentLocation(privatePages);
  const [, , startNewRequest] = useCurrentDecisionRequest();
  useEffect(() => {
    if (location.name === undefined) {
      navigateTo(privatePages.dashboard.url({}));
    }
  }, [location]);
  useExpireSessionAfter1hr();

  switch (location.name) {
    case undefined: {
      return <Fragment />;
    }
    case "profile": {
      return <Profile />;
    }
    case "decide": {
      return (
        <DecisionWizard
        officer={officer}
          account={location.values.cid}
          formId={
            location.params.formId ? location.params.formId[0] : undefined
          }
          onMove={(step) => {
            if (!step) {
              if (location.values.cid) {
                navigateTo(
                  privatePages.account.url({ cid: location.values.cid }),
                );
              } else {
                navigateTo(privatePages.dashboard.url({}));
              }
            } else {
              navigateTo(
                privatePages.decideWithStep.url({
                  cid: location.values.cid,
                  step,
                }),
              );
            }
          }}
        />
      );
    }
    case "decideWithStep": {
      return (
        <DecisionWizard
        officer={officer}
          account={location.values.cid}
          formId={
            location.params.formId ? location.params.formId[0] : undefined
          }
          step={location.values.step as WizardSteps}
          onMove={(step) => {
            if (!step) {
              if (location.values.cid) {
                navigateTo(
                  privatePages.account.url({ cid: location.values.cid }),
                );
              } else {
                navigateTo(privatePages.dashboard.url({}));
              }
            } else {
              navigateTo(
                privatePages.decideWithStep.url({
                  cid: location.values.cid,
                  step,
                }),
              );
            }
          }}
        />
      );
    }
    case "decideNew": {
      return (
        <DecisionWizard
        officer={officer}
          account={location.values.cid}
          newPayto={decodeCrockFromURI(location.values.payto)}
          formId={
            location.params.formId ? location.params.formId[0] : undefined
          }
          onMove={(step) => {
            if (!step) {
              if (location.values.cid) {
                navigateTo(
                  privatePages.account.url({ cid: location.values.cid }),
                );
              } else {
                navigateTo(privatePages.dashboard.url({}));
              }
            } else {
              navigateTo(
                privatePages.decideNewWithStep.url({
                  cid: location.values.cid,
                  payto: location.values.payto,
                  step,
                }),
              );
            }
          }}
        />
      );
    }
    case "decideNewWithStep": {
      return (
        <DecisionWizard
          account={location.values.cid}
        officer={officer}
          newPayto={decodeCrockFromURI(location.values.payto)}
          formId={
            location.params.formId ? location.params.formId[0] : undefined
          }
          step={location.values.step as WizardSteps}
          onMove={(step) => {
            if (!step) {
              if (location.values.cid) {
                navigateTo(
                  privatePages.account.url({ cid: location.values.cid }),
                );
              } else {
                navigateTo(privatePages.dashboard.url({}));
              }
            } else {
              navigateTo(
                privatePages.decideNewWithStep.url({
                  cid: location.values.cid,
                  payto: location.values.payto,
                  step,
                }),
              );
            }
          }}
        />
      );
    }
    case "account": {
      return (
        <AccountDetails
          account={location.values.cid}
          routeToShowTransfers={privatePages.transfersForAccount}
          routeToShowCollectedInfo={privatePages.showCollectedInfo}
          onNewDecision={(r) => {
            startNewRequest(r);
            navigateTo(
              privatePages.decide.url({
                cid: location.values.cid,
              }),
            );
          }}
        />
      );
    }
    case "accounts": {
      return <AccountList routeToAccountById={privatePages.account} />;
    }
    case "search": {
      return (
        <Search
          routeToAccountById={privatePages.account}
          onNewDecision={(account, payto) => {
            startNewRequest();
            navigateTo(
              privatePages.decideNew.url({
                cid: account,
                payto: payto,
              }),
            );
          }}
        />
      );
    }
    case "statsDownload": {
      return <div>not yet implemented</div>;
    }
    case "dashboard": {
      return <Dashboard />;
    }
    case "transfers": {
      return <Transfers routeToAccountById={privatePages.account} />;
    }
    case "transfersForAccount": {
      return (
        <Transfers
          routeToAccountById={privatePages.account}
          account={location.values.cid}
        />
      );
    }
    case "showCollectedInfo": {
      return (
        <ShowCollectedInfo
          routeToAccountById={privatePages.account}
          routeToShowCollectedInfo={privatePages.showCollectedInfo}
          account={location.values.cid}
          rowId={Number.parseInt(location.values.rowId, 10)}
        />
      );
    }

    default:
      assertUnreachable(location);
  }
}

function Navigation(): VNode {
  const { i18n } = useTranslationContext();
  const pageList = [
    {
      route: privatePages.dashboard,
      Icon: PeopleIcon,
      label: i18n.str`Dashboard`,
    },
    { route: privatePages.accounts, Icon: HomeIcon, label: i18n.str`Accounts` },
    {
      route: privatePages.transfers,
      Icon: TransfersIcon,
      label: i18n.str`Transfers`,
    },
    {
      route: privatePages.search,
      Icon: SearchIcon,
      label: i18n.str`Search`,
    },
    { route: privatePages.profile, Icon: PeopleIcon, label: i18n.str`Profile` },
  ];
  const { path } = useNavigationContext();
  return (
    <div class="hidden sm:block min-w-min bg-indigo-600 divide-y rounded-r-lg divide-cyan-800 overflow-y-auto overflow-x-clip">
      <nav class="flex flex-1 flex-col mx-4 mt-4 mb-2">
        <ul role="list" class="flex flex-1 flex-col gap-y-7">
          <li>
            <ul role="list" class="-mx-2 space-y-1">
              {pageList.map((p, idx) => {
                if (!p) return undefined;
                return (
                  <li key={idx}>
                    <a
                      href={p.route.url({})}
                      data-selected={path == p.route.url({})}
                      class="data-[selected=true]:bg-indigo-700 pr-4 data-[selected=true]:text-white  text-indigo-200 hover:text-white hover:bg-indigo-700   group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold"
                    >
                      {p.Icon && <p.Icon />}
                      <span class="hidden md:inline">{p.label}</span>
                    </a>
                  </li>
                );
              })}
            </ul>
          </li>
        </ul>
      </nav>
    </div>
  );
}
