import { useState } from 'react';
import { useForm } from '@inertiajs/react';
import { Eye, EyeOff, KeyRound, Loader2, ShieldAlert } from 'lucide-react';
import AppLayout from '@/Layouts/AppLayout';
import { TopBar, CrumbSep, CrumbCurrent } from '@/Components/Table';
import { FormInput } from '@/Components/Form/FormControls';
import { useToast } from '@/Components/Toast';

/**
 * Settings > Change Password — the self-service half of finding F-05.
 *
 * TWO SHAPES, ONE SERVER PROP:
 *   forced === false → the user came here deliberately, so they must re-type their CURRENT
 *                      password before setting a new one.
 *   forced === true  → an admin just reset this account. The current-password field is not
 *                      rendered AT ALL (not disabled, not pre-filled): they proved the password
 *                      seconds ago at login, and the plaintext is deliberately never kept in the
 *                      session for pre-filling.
 *
 * ⚠️ `forced` is DISPLAY-ONLY. The server re-derives it from `users.PasswordResetTokenCreatedAt`
 * on the authenticated user (ChangePasswordRequest::isForced()) and never trusts this value back.
 * A non-forced session that posts the forced-shaped payload is still asked for the current
 * password — do not "optimise" that by sending a flag.
 *
 * Notifications follow .claude/rules/notifications.md exactly: inline field errors here, the
 * generic 422 toast from onError, and NO onSuccess toast — ChangePasswordController flashes
 * 'Password berhasil diubah.' itself.
 *
 * The label/error/aria-invalid wrapper is local rather than Pengelolaan/FormPage's `Field`:
 * that module documents itself as scaffolding for the Pengelolaan > Employee forms, and
 * `Components/Form/FormControls` (which this does use) ships the control only — the inline
 * error and aria-invalid are the caller's job everywhere in this app.
 */

const SUBMIT_BTN =
    'inline-flex h-9 items-center justify-center gap-1.5 rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-50 disabled:hover:brightness-100';

/** Label + password control + reveal toggle + inline error. */
function PasswordField({ form, name, label, autoComplete, hint }) {
    const [visible, setVisible] = useState(false);
    const error = form.errors[name];

    return (
        <div>
            <label htmlFor={name} className="mb-1 block text-sm font-medium text-foreground">
                {label} <span className="text-destructive">*</span>
            </label>
            <div className="relative">
                <FormInput
                    id={name}
                    name={name}
                    type={visible ? 'text' : 'password'}
                    autoComplete={autoComplete}
                    value={form.data[name] ?? ''}
                    onChange={(e) => form.setData(name, e.target.value)}
                    aria-invalid={error ? 'true' : undefined}
                    aria-describedby={error ? `${name}-error` : undefined}
                    className="pr-9"
                />
                <button
                    type="button"
                    onClick={() => setVisible((v) => !v)}
                    aria-label={visible ? `Sembunyikan ${label}` : `Tampilkan ${label}`}
                    className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-gray-500 transition-colors hover:text-primary"
                >
                    {visible ? <EyeOff className="size-4" aria-hidden="true" /> : <Eye className="size-4" aria-hidden="true" />}
                </button>
            </div>
            {hint && !error && <p className="mt-1 text-xs text-muted-foreground">{hint}</p>}
            {error && <p id={`${name}-error`} className="mt-1 text-xs text-destructive">{error}</p>}
        </div>
    );
}

/**
 * What actually goes on the wire. The current-password key is absent ENTIRELY on the forced flow
 * — an empty string would still be a value the server has to reason about.
 *
 * A pure function, and applied through form.transform() rather than by seeding useForm with
 * different shapes, because useForm captures its initial data once: if `forced` flips between
 * renders (the marker is cleared in another tab, or a 422 comes back and the page is re-rendered
 * with a freshly derived `forced`), a conditionally-seeded form has no `current_password` key at
 * all, so the field it now has to render is uncontrolled and the value never reaches the server.
 * Seeding all three keys and stripping at submit time has no such state.
 */
export function changePasswordPayload(data, forced) {
    if (! forced) {
        return data;
    }

    const { current_password: _dropped, ...rest } = data;

    return rest;
}

export default function ChangePassword({ forced = false }) {
    const { show: showToast } = useToast();

    const form = useForm({ current_password: '', password: '', password_confirmation: '' });

    // `forced` is derived at RENDER time and re-derived at SUBMIT time, so the two can disagree:
    // if the marker is cleared in between, the server answers 422 with `current_password`
    // required while this page is still rendering the forced shape, which has no such field —
    // an error with nowhere to land and no way forward but a manual reload. Rendering the field
    // whenever that error is present costs nothing and closes the dead end.
    const needsCurrentPassword = ! forced || Boolean(form.errors.current_password);

    const submit = (e) => {
        e.preventDefault();
        form.transform((data) => changePasswordPayload(data, forced));
        form.put(route('settings.password.update'), {
            preserveScroll: true,
            // Not a toast: the server sends that. This only empties the boxes after a voluntary
            // change, which redirects back to this same page.
            onSuccess: () => form.reset(),
            onError: () => showToast('Please check the form and try again.', 'error'),
        });
    };

    return (
        <div className="space-y-4">
            {/* Both crumbs are CrumbCurrent: Settings is a popup, not a page — there is no
                /settings URL for a Link to point at. Same reasoning as the log viewer. */}
            <TopBar
                title="Change password"
                breadcrumb={<><CrumbCurrent>Settings</CrumbCurrent><CrumbSep /><CrumbCurrent>Change password</CrumbCurrent></>}
            />

            {forced && (
                <div className="flex items-start gap-3 rounded-xl border border-amber-300 bg-amber-50 p-4 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-200">
                    <ShieldAlert className="mt-0.5 size-5 shrink-0" aria-hidden="true" />
                    <div className="text-[13px] leading-relaxed">
                        <p className="font-semibold">Password Anda harus diganti sekarang.</p>
                        <p className="mt-1">
                            Seorang administrator baru saja mereset password akun ini dan memberi Anda password
                            sementara. Password sementara itu hanya untuk masuk sekali — Anda tidak bisa memakai
                            halaman lain sebelum menggantinya dengan password pilihan Anda sendiri.
                        </p>
                    </div>
                </div>
            )}

            <div className="rounded-xl border border-border bg-card shadow-sm">
                <div className="flex items-center gap-2 border-b border-border px-5 py-3">
                    <KeyRound className="size-4 text-primary" aria-hidden="true" />
                    <h2 className="text-sm font-semibold text-text-heading">
                        {forced ? 'Pilih password baru' : 'Ganti password Anda'}
                    </h2>
                </div>

                <form onSubmit={submit} className="grid max-w-md gap-4 p-5">
                    {/* Absent on the forced flow — see the file docblock. Not disabled, not
                        pre-filled: there is nothing to pre-fill it from. It comes back if the
                        server ever asks for it anyway (see needsCurrentPassword). */}
                    {needsCurrentPassword && (
                        <PasswordField
                            form={form}
                            name="current_password"
                            label="Password saat ini"
                            autoComplete="current-password"
                        />
                    )}

                    <PasswordField
                        form={form}
                        name="password"
                        label="Password baru"
                        autoComplete="new-password"
                        hint="Minimal 6 karakter, dengan setidaknya satu huruf besar, satu huruf kecil, dan satu angka."
                    />

                    <PasswordField
                        form={form}
                        name="password_confirmation"
                        label="Ulangi password baru"
                        autoComplete="new-password"
                    />

                    {/* Left-aligned, primary FIRST (.claude/rules/ui-conventions.md). Nothing else
                        sits here on the forced flow: no Cancel, no Back — any control that looks
                        like it dismisses the requirement would be a lie, because
                        EnsurePasswordChanged sends the user straight back here. */}
                    <div className="flex items-center justify-start gap-2 pt-1">
                        <button type="submit" disabled={form.processing} className={SUBMIT_BTN}>
                            {form.processing
                                ? <><Loader2 className="mr-1 size-4 animate-spin" aria-hidden="true" />Menyimpan...</>
                                : 'Simpan password baru'}
                        </button>
                    </div>
                </form>
            </div>
        </div>
    );
}

ChangePassword.layout = [AppLayout];
