import { useEffect, useMemo, useState, type FormEvent } from "react";
import { Link, useParams } from "react-router-dom";
import {
  Button,
  Card,
  DismissibleAlert,
  FormField,
  LoadingFrame,
  formatDateTime,
  type ApiSettings,
  type AuthInfo
} from "@govoplan/core-webui";
import {
  getPublicSchedulingParticipation,
  submitPublicSchedulingParticipation,
  type SchedulingAvailabilityValue,
  type SchedulingPublicParticipationAccessPayload,
  type SchedulingPublicParticipationResponse
} from "../../api/scheduling";
import { applySchedulingAvailabilityChoice } from "./schedulingViewModel";

type SchedulingPublicPageProps = {
  settings: ApiSettings;
  auth: AuthInfo | null;
};

const I18N = {
  accessDetails: "i18n:govoplan-scheduling.access_details.79c06b89",
  accessHelp: "i18n:govoplan-scheduling.enter_the_details_supplied_with_the_invitation_for_privacy.81ba419c",
  accessRequest: "i18n:govoplan-scheduling.open_scheduling_request.31829cce",
  alreadySubmitted: "i18n:govoplan-scheduling.responses_may_be_updated_while_this_request_remains_open.4faecbbe",
  answerRequired: "i18n:govoplan-scheduling.choose_availability_for_at_least_one_candidate_slot.28d2111f",
  available: "i18n:govoplan-scheduling.available.7c62a142",
  backToScheduling: "i18n:govoplan-scheduling.open_in_scheduling.48df1541",
  comment: "i18n:govoplan-scheduling.comment.d03495b1",
  cancelled: "i18n:govoplan-scheduling.this_scheduling_request_was_cancelled.1af3c85e",
  cancellationNoticeUntil: "i18n:govoplan-scheduling.cancellation_notice_available_until.f840d1e6",
  deadline: "i18n:govoplan-scheduling.response_deadline.7fd9e3aa",
  email: "i18n:govoplan-scheduling.participant_email.2cadfd9e",
  invalidAccess: "i18n:govoplan-scheduling.this_scheduling_link_is_invalid_expired_or_the_access_details.8e7aa197",
  loading: "i18n:govoplan-scheduling.loading_scheduling_request.43c39c1b",
  maybe: "i18n:govoplan-scheduling.maybe.56dd8d0b",
  noLongerOpen: "i18n:govoplan-scheduling.this_scheduling_request_is_no_longer_accepting_responses.c612e78a",
  password: "i18n:govoplan-scheduling.guest_password.94545e82",
  response: "i18n:govoplan-scheduling.your_availability.f86c8215",
  saved: "i18n:govoplan-scheduling.your_response_has_been_recorded.b855088d",
  submit: "i18n:govoplan-scheduling.submit_response.a5f0c053",
  unavailable: "i18n:govoplan-scheduling.unavailable.2c9c1f79"
} as const;

function accessPayload(email: string, password: string): SchedulingPublicParticipationAccessPayload {
  return {
    participant_email: email.trim() || null,
    password: password || null
  };
}

function initialAvailability(response: SchedulingPublicParticipationResponse): Record<string, SchedulingAvailabilityValue | ""> {
  const previous = new Map(response.answers.map((answer) => [answer.slot_id, answer.value]));
  return Object.fromEntries(response.slots.map((slot) => [slot.id, previous.get(slot.id) ?? ""]));
}

function newIdempotencyKey(): string {
  if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
  return `scheduling-response-${Date.now()}-${Math.random().toString(16).slice(2)}`;
}

export default function SchedulingPublicPage({ settings, auth }: SchedulingPublicPageProps) {
  const { requestId = "", token = "" } = useParams();
  const [response, setResponse] = useState<SchedulingPublicParticipationResponse | null>(null);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [availability, setAvailability] = useState<Record<string, SchedulingAvailabilityValue | "">>({});
  const [comment, setComment] = useState("");
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [accessAttempted, setAccessAttempted] = useState(false);
  const [error, setError] = useState("");
  const [success, setSuccess] = useState("");

  const slotIds = useMemo(() => response?.slots.map((slot) => slot.id) ?? [], [response]);
  const collecting = response?.status === "collecting";

  function applyResponse(next: SchedulingPublicParticipationResponse) {
    setResponse(next);
    setAvailability(initialAvailability(next));
    setComment(next.comment ?? "");
    setError("");
  }

  useEffect(() => {
    let cancelled = false;
    setLoading(true);
    setResponse(null);
    setAccessAttempted(false);
    setError("");
    void getPublicSchedulingParticipation(settings, requestId, token, {})
      .then((next) => {
        if (!cancelled) applyResponse(next);
      })
      .catch(() => undefined)
      .finally(() => {
        if (!cancelled) setLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [requestId, settings.apiBaseUrl, settings.apiKey, token]);

  async function openRequest(event: FormEvent) {
    event.preventDefault();
    setLoading(true);
    setAccessAttempted(true);
    setError("");
    try {
      applyResponse(await getPublicSchedulingParticipation(settings, requestId, token, accessPayload(email, password)));
    } catch {
      setResponse(null);
      setError(I18N.invalidAccess);
    } finally {
      setLoading(false);
    }
  }

  async function saveResponse(event: FormEvent) {
    event.preventDefault();
    if (!response) return;
    if (!response.slots.some((slot) => availability[slot.id])) {
      setError(I18N.answerRequired);
      return;
    }
    setSaving(true);
    setError("");
    setSuccess("");
    try {
      const next = await submitPublicSchedulingParticipation(settings, requestId, token, {
        ...accessPayload(email, password),
        answers: response.slots
          .filter((slot) => availability[slot.id])
          .map((slot) => ({
            slot_id: slot.id,
            value: availability[slot.id] as SchedulingAvailabilityValue,
            option_revision: slot.revision
          })),
        comment: response.allow_comments ? comment : null,
        idempotency_key: newIdempotencyKey()
      });
      applyResponse(next);
      setSuccess(I18N.saved);
    } catch {
      setError(I18N.invalidAccess);
    } finally {
      setSaving(false);
    }
  }

  return (
    <main className="scheduling-public-page">
      <LoadingFrame loading={loading} label={I18N.loading}>
        {auth && (
          <div className="scheduling-public-deep-link">
            <Link className="btn btn-secondary" to={`/scheduling?request_id=${encodeURIComponent(requestId)}`}>
              {I18N.backToScheduling}
            </Link>
          </div>
        )}

        {!response && !loading && (
          <Card title={I18N.accessDetails}>
            <form className="scheduling-public-access-form" onSubmit={openRequest}>
              <p className="muted">{I18N.accessHelp}</p>
              {accessAttempted && error && <DismissibleAlert tone="danger">{error}</DismissibleAlert>}
              <div className="form-grid two-col">
                <FormField label={I18N.email}>
                  <input
                    type="email"
                    autoComplete="email"
                    value={email}
                    onChange={(event) => setEmail(event.target.value)}
                  />
                </FormField>
                <FormField label={I18N.password}>
                  <input
                    type="password"
                    autoComplete="current-password"
                    value={password}
                    onChange={(event) => setPassword(event.target.value)}
                  />
                </FormField>
              </div>
              <div className="scheduling-public-actions">
                <Button type="submit" variant="primary">{I18N.accessRequest}</Button>
              </div>
            </form>
          </Card>
        )}

        {response && (
          <form className="scheduling-public-content" onSubmit={saveResponse}>
            <Card title={response.title}>
              {response.description && <p className="scheduling-public-description">{response.description}</p>}
              <dl className="scheduling-public-summary">
                {response.location && <><dt>i18n:govoplan-scheduling.location.d219c681</dt><dd>{response.location}</dd></>}
                {response.deadline_at && <><dt>{I18N.deadline}</dt><dd>{formatDateTime(response.deadline_at)}</dd></>}
              </dl>
              {response.cancellation_notice_only
                ? <DismissibleAlert tone="warning" dismissible={false}>{I18N.cancelled}</DismissibleAlert>
                : !collecting && <DismissibleAlert tone="info" dismissible={false}>{I18N.noLongerOpen}</DismissibleAlert>}
              {response.has_response && collecting && <DismissibleAlert tone="info" dismissible={false}>{I18N.alreadySubmitted}</DismissibleAlert>}
              {response.cancellation_notice_only && response.cancellation_notice_until && (
                <p className="muted">{I18N.cancellationNoticeUntil}: {formatDateTime(response.cancellation_notice_until)}</p>
              )}
            </Card>

            {error && <DismissibleAlert tone="danger">{error}</DismissibleAlert>}
            {success && <DismissibleAlert tone="success">{success}</DismissibleAlert>}

            {!response.cancellation_notice_only && <Card title={I18N.response}>
              <div className="scheduling-public-slots">
                {response.slots.map((slot) => (
                  <fieldset className="scheduling-public-slot" key={slot.id} disabled={!collecting || saving}>
                    <legend>{slot.label}</legend>
                    <p>{formatDateTime(slot.start_at)} – {formatDateTime(slot.end_at)}</p>
                    {slot.description && <p className="muted">{slot.description}</p>}
                    {(slot.location || response.location) && <p className="muted">{slot.location || response.location}</p>}
                    <div className="scheduling-public-choice-group">
                      {([
                        ["available", I18N.available],
                        ...(response.allow_maybe ? [["maybe", I18N.maybe] as const] : []),
                        ["unavailable", I18N.unavailable]
                      ] as Array<[SchedulingAvailabilityValue, string]>).map(([value, label]) => (
                        <label key={value}>
                          <input
                            type="radio"
                            name={`slot-${slot.id}`}
                            value={value}
                            checked={availability[slot.id] === value}
                            onChange={() => setAvailability((current) => applySchedulingAvailabilityChoice(
                              slotIds,
                              current,
                              slot.id,
                              value,
                              response.single_choice
                            ))}
                          />
                          <span>{label}</span>
                        </label>
                      ))}
                    </div>
                  </fieldset>
                ))}
              </div>
              {response.allow_comments && (
                <FormField label={I18N.comment}>
                  <textarea
                    rows={4}
                    maxLength={4000}
                    disabled={!collecting || saving}
                    value={comment}
                    onChange={(event) => setComment(event.target.value)}
                  />
                </FormField>
              )}
              {collecting && (
                <div className="scheduling-public-actions">
                  <Button type="submit" variant="primary" disabled={saving}>{I18N.submit}</Button>
                </div>
              )}
            </Card>}
          </form>
        )}
      </LoadingFrame>
    </main>
  );
}
