#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"

#define FRAME_CFG_SSID_MAX_LEN 32
#define FRAME_CFG_PASSWORD_MAX_LEN 64
#define FRAME_CFG_SERVER_MAX_LEN 128
#define FRAME_CFG_TOKEN_MAX_LEN 64
#define FRAME_AP_PASSWORD_LEN 10

#define FRAME_DEVICE_ID_LEN 12 /* 6-byte STA MAC as lowercase hex */

typedef struct {
    char sta_ssid[FRAME_CFG_SSID_MAX_LEN + 1];
    char sta_password[FRAME_CFG_PASSWORD_MAX_LEN + 1];
    char toolsserver[FRAME_CFG_SERVER_MAX_LEN + 1];
    char access_token[FRAME_CFG_TOKEN_MAX_LEN + 1]; /* optional; legacy shared MANAGEMENT_TOKEN */
    /* Per-frame token issued by the server via GET /frame/config after
     * this device first introduces itself by id -- preferred over
     * access_token once present (see frame_client.c's build_url). Not
     * set at the captive portal; empty until the server pushes one. */
    char device_token[FRAME_CFG_TOKEN_MAX_LEN + 1];
} frame_config_t;

/**
 * This device's stable identity as reported to the server (?id= on every
 * request): the full 6-byte STA MAC as 12 lowercase hex chars. Derived
 * from the same MAC the provisioning AP SSID suffix comes from; never
 * stored. out must hold at least FRAME_DEVICE_ID_LEN + 1 bytes.
 */
void frame_device_id_get(char *out, size_t out_size);

/**
 * Persists (only) the server-issued per-frame device token -- called
 * from the wake cycle when GET /frame/config delivers one. Deliberately
 * touches nothing else: unlike frame_config_save() it must not reset
 * the connected-once flag or invalidate the WiFi fast-connect cache,
 * since nothing about the network changed.
 */
void frame_config_set_device_token(const char *token);

/**
 * Loads the saved home-network config from NVS.
 * Returns ESP_ERR_NVS_NOT_FOUND if the device has never been provisioned.
 */
esp_err_t frame_config_load(frame_config_t *out);

/** Saves the home-network config to NVS. Resets the "connected once"
 * flag below, since this is a fresh (re)provisioning event. */
esp_err_t frame_config_save(const frame_config_t *cfg);

/**
 * Whether the device has already shown the post-connect status screen at
 * least once since the current WiFi config was saved. Used so the status
 * screen always shows on the first connection after (re)provisioning, but
 * is skipped on later successful wakes to save an extra refresh.
 */
bool frame_config_has_connected_once(void);

/** Marks the status screen as having been shown for the current WiFi config. */
void frame_config_mark_connected_once(void);

/**
 * Erases the stored home-network config (SSID/password/tools server) so the
 * device falls back into provisioning on its next boot. Leaves the softAP
 * identity (SSID/password) untouched, since that's tied to the device
 * itself, not a particular home network -- regenerating it on every reset
 * would force re-scanning the join QR code for no reason. Also leaves the
 * last-displayed-photo CRC (below) untouched -- it describes what's
 * physically on screen, not network config, and stays valid regardless.
 */
void frame_config_clear(void);

/**
 * Returns the CRC32 of the last frame actually written to the panel via a
 * physical refresh. Returns ESP_ERR_NVS_NOT_FOUND if nothing's been
 * displayed yet.
 */
esp_err_t frame_config_get_last_display_crc32(uint32_t *out);

/** Records the CRC32 of the frame just displayed, for next time. */
void frame_config_set_last_display_crc32(uint32_t crc32);

/**
 * Invalidates the tracked last-displayed-photo CRC. Call this whenever
 * something other than a tracked photo fetch writes to the panel (status
 * screens, QR onboarding) -- otherwise a later photo fetch that happens
 * to produce the same CRC as whatever photo was showing *before* the
 * panel got overwritten would wrongly skip refreshing back onto it,
 * leaving the other screen stuck on-screen indefinitely.
 */
void frame_config_invalidate_last_display_crc32(void);

/**
 * Returns this device's provisioning AP identity: a fixed SSID (from
 * Kconfig) and a password that's generated once on first use and persisted
 * in NVS from then on. The password is drawn from an easy-to-type charset
 * since it's shown on the e-ink panel (as both a QR code and plaintext) and
 * may need to be typed in by hand.
 */
void ap_identity_get(char *ssid_out, size_t ssid_len, char *pass_out, size_t pass_len);

/**
 * Brings up the ESPRESSO softAP + captive portal (DNS + HTTP) so the user
 * can provision the device. Does not return.
 */
void wifi_provisioning_start(void);

/**
 * Cached parameters from the most recent successful home-WiFi connection,
 * letting the next wake's first connect attempt skip the all-channel scan
 * (known BSSID/channel) and DHCP (known static IP/netmask/gateway/DNS).
 * Fields are stored exactly as esp-wifi/esp-netif already use them
 * internally, so they can be fed straight back in with no conversion.
 */
typedef struct {
    uint8_t bssid[6];
    uint8_t channel;
    uint32_t ip;
    uint32_t netmask;
    uint32_t gateway;
    uint32_t dns; /* 0 = none cached (best-effort; a real DHCP fallback still repopulates this) */
} frame_wifi_cache_t;

/**
 * Loads the cached fast-connect parameters. Returns false if there's
 * nothing cached yet, or it was invalidated (see frame_wifi_cache_clear).
 */
bool frame_wifi_cache_load(frame_wifi_cache_t *out);

/** Saves fast-connect parameters after a successful home-WiFi connection. */
void frame_wifi_cache_save(const frame_wifi_cache_t *cache);

/**
 * Clears the fast-connect cache. Called after a cached fast-connect
 * attempt itself fails (BSSID/channel went stale), after a full fetch
 * cycle fails despite a successful connection (the cached static IP may
 * be unreachable even though the link came up), and by
 * frame_config_save()/frame_config_clear() -- a (re)provisioning event
 * means whatever was cached may belong to a different network entirely.
 */
void frame_wifi_cache_clear(void);
