/*
 * DWN - Desktop Window Manager
 * Abstract Client Implementation
 */

#include "core/wm_client.h"
#include "core/wm_hashmap.h"
#include "core/wm_list.h"
#include "client.h"
#include "dwn.h"
#include "util.h"

#include <stdlib.h>
#include <string.h>
#include <assert.h>

/*==============================================================================
 * Internal Structures
 *============================================================================*/

struct AbstractClient {
    /* Core identification */
    WmClientId id;
    WmWindowHandle window;
    WmWindowHandle frame;
    WmClientType type;
    
    /* Legacy client pointer (for compatibility during migration) */
    Client *legacy;
    bool owns_legacy;
    
    /* State */
    WmClientState state;
    WmWorkspaceId workspace;
    
    /* Geometry */
    WmRect geometry;
    WmRect saved_geometry;
    int border_width;
    
    /* Properties */
    char title[256];
    char class_name[64];
    char instance_name[64];
    WmColor color;
    
    /* List links */
    AbstractClient *next;
    AbstractClient *prev;
    AbstractClient *mru_next;
    AbstractClient *mru_prev;
};

struct WmClientManager {
    AbstractClient *clients;      /* Global client list */
    AbstractClient *clients_tail; /* Tail of global list */
    AbstractClient *mru_head;     /* Most recently used */
    AbstractClient *mru_tail;     /* Least recently used */
    
    WmHashMap *by_window;         /* Map: window handle -> client */
    WmHashMap *by_frame;          /* Map: frame handle -> client */
    WmHashMap *by_id;             /* Map: client ID -> client */
    
    AbstractClient *focused;
    WmClientId next_id;
    int count;
};

/*==============================================================================
 * Global State
 *============================================================================*/

static WmClientManager *g_client_manager = NULL;

/*==============================================================================
 * Internal Helpers
 *============================================================================*/

static WmClientId generate_client_id(void) {
    if (!g_client_manager) return 0;
    return g_client_manager->next_id++;
}

static void link_client(AbstractClient *client) {
    if (!g_client_manager || !client) return;
    
    /* Add to global list */
    if (g_client_manager->clients_tail) {
        g_client_manager->clients_tail->next = client;
        client->prev = g_client_manager->clients_tail;
        g_client_manager->clients_tail = client;
    } else {
        g_client_manager->clients = client;
        g_client_manager->clients_tail = client;
    }
    
    /* Add to MRU list (at head = most recent) */
    if (g_client_manager->mru_head) {
        client->mru_next = g_client_manager->mru_head;
        g_client_manager->mru_head->mru_prev = client;
        g_client_manager->mru_head = client;
    } else {
        g_client_manager->mru_head = client;
        g_client_manager->mru_tail = client;
    }
    
    g_client_manager->count++;
}

static void unlink_client(AbstractClient *client) {
    if (!g_client_manager || !client) return;
    
    /* Remove from global list */
    if (client->prev) {
        client->prev->next = client->next;
    } else {
        g_client_manager->clients = client->next;
    }
    
    if (client->next) {
        client->next->prev = client->prev;
    } else {
        g_client_manager->clients_tail = client->prev;
    }
    
    /* Remove from MRU list */
    if (client->mru_prev) {
        client->mru_prev->mru_next = client->mru_next;
    } else {
        g_client_manager->mru_head = client->mru_next;
    }
    
    if (client->mru_next) {
        client->mru_next->mru_prev = client->mru_prev;
    } else {
        g_client_manager->mru_tail = client->mru_prev;
    }
    
    g_client_manager->count--;
}

/*==============================================================================
 * Client Lifecycle
 *============================================================================*/

AbstractClient* wm_client_create(WmWindowHandle window, WmClientType type) {
    if (!window) return NULL;
    
    AbstractClient *client = calloc(1, sizeof(AbstractClient));
    if (!client) return NULL;
    
    client->id = generate_client_id();
    client->window = window;
    client->type = type;
    client->state = WM_CLIENT_STATE_NORMAL | WM_CLIENT_STATE_MAPPED;
    client->workspace = 0;
    client->border_width = 0;
    client->color = 0;
    
    /* Register with manager */
    if (g_client_manager) {
        link_client(client);
        wm_hashmap_insert(g_client_manager->by_window, window, client);
        
        int id_key = (int)client->id;
        wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
    }
    
    LOG_DEBUG("Created abstract client %u for window %p", client->id, window);
    return client;
}

void wm_client_destroy(AbstractClient *client) {
    if (!client) return;
    
    LOG_DEBUG("Destroying abstract client %u", client->id);
    
    /* Remove from manager */
    if (g_client_manager) {
        wm_hashmap_remove(g_client_manager->by_window, client->window);
        if (client->frame) {
            wm_hashmap_remove(g_client_manager->by_frame, client->frame);
        }
        int id_key = (int)client->id;
        wm_hashmap_remove(g_client_manager->by_id, (void*)(intptr_t)id_key);
        
        unlink_client(client);
        
        /* Clear focus if this was focused */
        if (g_client_manager->focused == client) {
            g_client_manager->focused = NULL;
        }
    }
    
    /* Destroy legacy client if we own it */
    if (client->legacy && client->owns_legacy) {
        client_destroy(client->legacy);
    }
    
    free(client);
}

bool wm_client_is_valid(const AbstractClient *client) {
    return client != NULL && 
           !(client->state & WM_CLIENT_STATE_DESTROYING);
}

/*==============================================================================
 * Native Access
 *============================================================================*/

WmWindowHandle wm_client_get_window(const AbstractClient *client) {
    return client ? client->window : NULL;
}

WmWindowHandle wm_client_get_frame(const AbstractClient *client) {
    return client ? client->frame : NULL;
}

Client* wm_client_get_legacy(const AbstractClient *client) {
    return client ? client->legacy : NULL;
}

AbstractClient* wm_client_from_legacy(Client *legacy_client) {
    if (!legacy_client) return NULL;
    
    /* Check if we already have a wrapper for this client */
    AbstractClient *existing = wm_client_find_by_window((WmWindowHandle)legacy_client->window);
    if (existing) return existing;
    
    /* Create wrapper */
    AbstractClient *client = calloc(1, sizeof(AbstractClient));
    if (!client) return NULL;
    
    client->id = generate_client_id();
    client->window = (WmWindowHandle)legacy_client->window;
    client->frame = (WmWindowHandle)legacy_client->frame;
    client->legacy = legacy_client;
    client->owns_legacy = false;
    
    /* Sync state from legacy */
    client->workspace = legacy_client->workspace;
    client->border_width = legacy_client->border_width;
    client->geometry.x = legacy_client->x;
    client->geometry.y = legacy_client->y;
    client->geometry.width = legacy_client->width;
    client->geometry.height = legacy_client->height;
    
    size_t title_len = strlen(legacy_client->title);
    if (title_len >= sizeof(client->title)) title_len = sizeof(client->title) - 1;
    memcpy(client->title, legacy_client->title, title_len);
    client->title[title_len] = '\0';
    
    size_t class_len = strlen(legacy_client->class);
    if (class_len >= sizeof(client->class_name)) class_len = sizeof(client->class_name) - 1;
    memcpy(client->class_name, legacy_client->class, class_len);
    client->class_name[class_len] = '\0';
    client->color = legacy_client->taskbar_color;
    
    /* Convert flags */
    if (legacy_client->flags & CLIENT_FLOATING) {
        client->state |= WM_CLIENT_STATE_FLOATING;
    }
    if (legacy_client->flags & CLIENT_FULLSCREEN) {
        client->state |= WM_CLIENT_STATE_FULLSCREEN;
    }
    if (legacy_client->flags & CLIENT_URGENT) {
        client->state |= WM_CLIENT_STATE_URGENT;
    }
    if (legacy_client->flags & CLIENT_MINIMIZED) {
        client->state |= WM_CLIENT_STATE_MINIMIZED;
    }
    if (legacy_client->flags & CLIENT_STICKY) {
        client->state |= WM_CLIENT_STATE_STICKY;
    }
    if (legacy_client->flags & CLIENT_MAXIMIZED) {
        client->state |= WM_CLIENT_STATE_MAXIMIZED;
    }
    
    /* Register */
    if (g_client_manager) {
        link_client(client);
        wm_hashmap_insert(g_client_manager->by_window, client->window, client);
        if (client->frame) {
            wm_hashmap_insert(g_client_manager->by_frame, client->frame, client);
        }
        int id_key = (int)client->id;
        wm_hashmap_insert(g_client_manager->by_id, (void*)(intptr_t)id_key, client);
    }
    
    return client;
}

/*==============================================================================
 * Client Identification
 *============================================================================*/

WmClientId wm_client_get_id(const AbstractClient *client) {
    return client ? client->id : 0;
}

WmClientType wm_client_get_type(const AbstractClient *client) {
    return client ? client->type : WM_CLIENT_TYPE_UNKNOWN;
}

void wm_client_set_type(AbstractClient *client, WmClientType type) {
    if (client) client->type = type;
}

/*==============================================================================
 * Client State
 *============================================================================*/

WmClientState wm_client_get_state(const AbstractClient *client) {
    return client ? client->state : 0;
}

void wm_client_set_state(AbstractClient *client, WmClientState state) {
    if (client) client->state = state;
}

void wm_client_add_state(AbstractClient *client, WmClientState state) {
    if (client) client->state |= state;
}

void wm_client_remove_state(AbstractClient *client, WmClientState state) {
    if (client) client->state &= ~state;
}

void wm_client_toggle_state(AbstractClient *client, WmClientState state) {
    if (client) client->state ^= state;
}

bool wm_client_has_state(const AbstractClient *client, WmClientState state) {
    return client && (client->state & state) != 0;
}

/*==============================================================================
 * Client Geometry
 *============================================================================*/

WmRect wm_client_get_geometry(const AbstractClient *client) {
    if (!client) return (WmRect){0, 0, 0, 0};
    
    /* Sync from legacy if available */
    if (client->legacy) {
        return (WmRect){
            client->legacy->x,
            client->legacy->y,
            client->legacy->width,
            client->legacy->height
        };
    }
    
    return client->geometry;
}

void wm_client_set_geometry(AbstractClient *client, const WmRect *geometry) {
    if (!client || !geometry) return;
    
    client->geometry = *geometry;
    
    /* Sync to legacy if available */
    if (client->legacy) {
        client->legacy->x = geometry->x;
        client->legacy->y = geometry->y;
        client->legacy->width = geometry->width;
        client->legacy->height = geometry->height;
    }
}

WmPoint wm_client_get_position(const AbstractClient *client) {
    WmRect geom = wm_client_get_geometry(client);
    return (WmPoint){geom.x, geom.y};
}

void wm_client_set_position(AbstractClient *client, int x, int y) {
    if (!client) return;
    
    client->geometry.x = x;
    client->geometry.y = y;
    
    if (client->legacy) {
        client->legacy->x = x;
        client->legacy->y = y;
    }
}

WmSize wm_client_get_size(const AbstractClient *client) {
    WmRect geom = wm_client_get_geometry(client);
    return (WmSize){geom.width, geom.height};
}

void wm_client_set_size(AbstractClient *client, int width, int height) {
    if (!client) return;
    
    client->geometry.width = width;
    client->geometry.height = height;
    
    if (client->legacy) {
        client->legacy->width = width;
        client->legacy->height = height;
    }
}

int wm_client_get_border_width(const AbstractClient *client) {
    if (!client) return 0;
    if (client->legacy) return client->legacy->border_width;
    return client->border_width;
}

void wm_client_set_border_width(AbstractClient *client, int width) {
    if (!client) return;
    
    client->border_width = width;
    
    if (client->legacy) {
        client->legacy->border_width = width;
    }
}

void wm_client_save_geometry(AbstractClient *client) {
    if (!client) return;
    
    client->saved_geometry = wm_client_get_geometry(client);
    
    if (client->legacy) {
        client->legacy->old_x = client->saved_geometry.x;
        client->legacy->old_y = client->saved_geometry.y;
        client->legacy->old_width = client->saved_geometry.width;
        client->legacy->old_height = client->saved_geometry.height;
    }
}

void wm_client_restore_geometry(AbstractClient *client) {
    if (!client) return;
    
    wm_client_set_geometry(client, &client->saved_geometry);
}

/*==============================================================================
 * Client Workspace
 *============================================================================*/

WmWorkspaceId wm_client_get_workspace(const AbstractClient *client) {
    return client ? client->workspace : 0;
}

void wm_client_set_workspace(AbstractClient *client, WmWorkspaceId workspace) {
    if (!client) return;
    
    client->workspace = workspace;
    
    if (client->legacy) {
        client->legacy->workspace = workspace;
    }
}

bool wm_client_is_on_workspace(const AbstractClient *client, WmWorkspaceId workspace) {
    if (!client) return false;
    if (client->state & WM_CLIENT_STATE_STICKY) return true;
    return client->workspace == workspace;
}

bool wm_client_is_visible_on_current(const AbstractClient *client) {
    if (!client || !dwn) return false;
    return wm_client_is_on_workspace(client, dwn->current_workspace);
}

/*==============================================================================
 * Client Properties
 *============================================================================*/

const char* wm_client_get_title(const AbstractClient *client) {
    if (!client) return "";
    if (client->legacy) return client->legacy->title;
    return client->title;
}

void wm_client_set_title(AbstractClient *client, const char *title) {
    if (!client || !title) return;
    
    strncpy(client->title, title, sizeof(client->title) - 1);
    client->title[sizeof(client->title) - 1] = '\0';
    
    if (client->legacy) {
        strncpy(client->legacy->title, title, sizeof(client->legacy->title) - 1);
        client->legacy->title[sizeof(client->legacy->title) - 1] = '\0';
    }
}

const char* wm_client_get_class(const AbstractClient *client) {
    if (!client) return "";
    if (client->legacy) return client->legacy->class;
    return client->class_name;
}

void wm_client_set_class(AbstractClient *client, const char *class_name) {
    if (!client || !class_name) return;
    
    strncpy(client->class_name, class_name, sizeof(client->class_name) - 1);
    client->class_name[sizeof(client->class_name) - 1] = '\0';
    
    if (client->legacy) {
        strncpy(client->legacy->class, class_name, sizeof(client->legacy->class) - 1);
        client->legacy->class[sizeof(client->legacy->class) - 1] = '\0';
    }
}

const char* wm_client_get_instance(const AbstractClient *client) {
    if (!client) return "";
    return client->instance_name;
}

void wm_client_set_instance(AbstractClient *client, const char *instance) {
    if (!client || !instance) return;
    
    strncpy(client->instance_name, instance, sizeof(client->instance_name) - 1);
    client->instance_name[sizeof(client->instance_name) - 1] = '\0';
}

WmColor wm_client_get_color(const AbstractClient *client) {
    if (!client) return 0;
    if (client->legacy) return client->legacy->taskbar_color;
    return client->color;
}

void wm_client_set_color(AbstractClient *client, WmColor color) {
    if (!client) return;
    
    client->color = color;
    
    if (client->legacy) {
        client->legacy->taskbar_color = color;
    }
}

/*==============================================================================
 * Client Actions (delegate to legacy for now)
 *============================================================================*/

void wm_client_focus(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_focus(client->legacy, true);
    }
    
    client->state |= WM_CLIENT_STATE_FOCUS;
    
    if (g_client_manager) {
        wm_client_manager_touch(g_client_manager, client);
        g_client_manager->focused = client;
    }
}

void wm_client_unfocus(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_unfocus(client->legacy);
    }
    
    client->state &= ~WM_CLIENT_STATE_FOCUS;
    
    if (g_client_manager && g_client_manager->focused == client) {
        g_client_manager->focused = NULL;
    }
}

void wm_client_raise(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_raise(client->legacy);
    }
}

void wm_client_lower(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_lower(client->legacy);
    }
}

void wm_client_show(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_show(client->legacy);
    }
    
    client->state |= WM_CLIENT_STATE_MAPPED;
}

void wm_client_hide(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_hide(client->legacy);
    }
    
    client->state &= ~WM_CLIENT_STATE_MAPPED;
}

void wm_client_minimize(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_minimize(client->legacy);
    }
    
    client->state |= WM_CLIENT_STATE_MINIMIZED;
}

void wm_client_restore(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_restore(client->legacy);
    }
    
    client->state &= ~WM_CLIENT_STATE_MINIMIZED;
}

void wm_client_close(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_close(client->legacy);
    }
}

void wm_client_kill(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_kill(client->legacy);
    }
}

/*==============================================================================
 * Client Management
 *============================================================================*/

void wm_client_reparent(AbstractClient *client, WmWindowHandle frame) {
    if (!client) return;
    
    client->frame = frame;
    
    /* Update frame lookup */
    if (g_client_manager) {
        if (client->frame) {
            wm_hashmap_remove(g_client_manager->by_frame, client->frame);
        }
        wm_hashmap_insert(g_client_manager->by_frame, frame, client);
    }
    
    if (client->legacy) {
        /* Legacy doesn't have a direct reparent function for frames */
    }
}

void wm_client_unreparent(AbstractClient *client) {
    if (!client) return;
    
    if (g_client_manager && client->frame) {
        wm_hashmap_remove(g_client_manager->by_frame, client->frame);
    }
    
    client->frame = NULL;
}

void wm_client_configure(AbstractClient *client) {
    if (!client) return;
    
    if (client->legacy) {
        client_configure(client->legacy);
    }
}

void wm_client_apply_size_hints(AbstractClient *client, int *width, int *height) {
    if (!client || !width || !height) return;
    
    if (client->legacy) {
        client_apply_size_hints(client->legacy, width, height);
    }
}

void wm_client_move_resize(AbstractClient *client, int x, int y, int width, int height) {
    if (!client) return;
    
    client->geometry.x = x;
    client->geometry.y = y;
    client->geometry.width = width;
    client->geometry.height = height;
    
    if (client->legacy) {
        client_move_resize(client->legacy, x, y, width, height);
    }
}

/*==============================================================================
 * Client List Management
 *============================================================================*/

AbstractClient* wm_client_get_next(const AbstractClient *client) {
    return client ? client->next : NULL;
}

AbstractClient* wm_client_get_prev(const AbstractClient *client) {
    return client ? client->prev : NULL;
}

AbstractClient* wm_client_get_first(void) {
    return g_client_manager ? g_client_manager->clients : NULL;
}

AbstractClient* wm_client_get_last(void) {
    return g_client_manager ? g_client_manager->clients_tail : NULL;
}

AbstractClient* wm_client_get_next_mru(const AbstractClient *client) {
    return client ? client->mru_next : NULL;
}

AbstractClient* wm_client_get_prev_mru(const AbstractClient *client) {
    return client ? client->mru_prev : NULL;
}

/*==============================================================================
 * Client Lookup
 *============================================================================*/

AbstractClient* wm_client_find_by_window(WmWindowHandle window) {
    if (!g_client_manager || !window) return NULL;
    return wm_hashmap_get(g_client_manager->by_window, window);
}

AbstractClient* wm_client_find_by_frame(WmWindowHandle frame) {
    if (!g_client_manager || !frame) return NULL;
    return wm_hashmap_get(g_client_manager->by_frame, frame);
}

AbstractClient* wm_client_find_by_id(WmClientId id) {
    if (!g_client_manager) return NULL;
    return wm_hashmap_get(g_client_manager->by_id, (void*)(intptr_t)id);
}

/*==============================================================================
 * Client Iteration
 *============================================================================*/

void wm_client_foreach(WmClientForeachFunc func, void *user_data) {
    if (!func) return;
    
    AbstractClient *client = wm_client_get_first();
    while (client) {
        AbstractClient *next = client->next;
        if (!func(client, user_data)) break;
        client = next;
    }
}

typedef struct {
    WmWorkspaceId workspace;
    WmClientForeachFunc func;
    void *user_data;
} ForeachWorkspaceData;

static bool foreach_workspace_wrapper(AbstractClient *client, void *data) {
    ForeachWorkspaceData *fwd = data;
    if (wm_client_is_on_workspace(client, fwd->workspace)) {
        return fwd->func(client, fwd->user_data);
    }
    return true;
}

void wm_client_foreach_on_workspace(WmWorkspaceId workspace,
                                     WmClientForeachFunc func, void *user_data) {
    ForeachWorkspaceData data = { workspace, func, user_data };
    wm_client_foreach(foreach_workspace_wrapper, &data);
}

int wm_client_count(void) {
    return g_client_manager ? g_client_manager->count : 0;
}

typedef struct {
    int count;
    WmWorkspaceId workspace;
} CountWorkspaceData;

static bool count_workspace(AbstractClient *client, void *data) {
    CountWorkspaceData *cwd = data;
    if (wm_client_is_on_workspace(client, cwd->workspace)) {
        cwd->count++;
    }
    return true;
}

int wm_client_count_on_workspace(WmWorkspaceId workspace) {
    CountWorkspaceData data = { 0, workspace };
    wm_client_foreach(count_workspace, &data);
    return data.count;
}

/*==============================================================================
 * Client Manager
 *============================================================================*/

WmClientManager* wm_client_manager_get(void) {
    return g_client_manager;
}

bool wm_client_manager_init(void) {
    if (g_client_manager) return true;
    
    g_client_manager = calloc(1, sizeof(WmClientManager));
    if (!g_client_manager) return false;
    
    g_client_manager->by_window = wm_hashmap_new();
    g_client_manager->by_frame = wm_hashmap_new();
    g_client_manager->by_id = wm_hashmap_new();
    
    if (!g_client_manager->by_window ||
        !g_client_manager->by_frame ||
        !g_client_manager->by_id) {
        wm_client_manager_shutdown();
        return false;
    }
    
    g_client_manager->next_id = 1;
    
    LOG_INFO("Client manager initialized");
    return true;
}

void wm_client_manager_shutdown(void) {
    if (!g_client_manager) return;
    
    /* Destroy all abstract clients (but not legacy ones - we don't own them) */
    AbstractClient *client = g_client_manager->clients;
    while (client) {
        AbstractClient *next = client->next;
        if (client->owns_legacy) {
            client_destroy(client->legacy);
        }
        free(client);
        client = next;
    }
    
    wm_hashmap_destroy(g_client_manager->by_window);
    wm_hashmap_destroy(g_client_manager->by_frame);
    wm_hashmap_destroy(g_client_manager->by_id);
    
    free(g_client_manager);
    g_client_manager = NULL;
    
    LOG_INFO("Client manager shutdown");
}

void wm_client_manager_add(WmClientManager *mgr, AbstractClient *client) {
    if (!mgr || !client) return;
    
    /* Client is already linked when created */
}

void wm_client_manager_remove(WmClientManager *mgr, AbstractClient *client) {
    if (!mgr || !client) return;
    
    /* Client is already unlinked when destroyed */
}

void wm_client_manager_touch(WmClientManager *mgr, AbstractClient *client) {
    if (!mgr || !client) return;
    
    /* Already at head - nothing to do */
    if (mgr->mru_head == client) return;
    
    /* Remove from current position */
    if (client->mru_prev) {
        client->mru_prev->mru_next = client->mru_next;
    }
    if (client->mru_next) {
        client->mru_next->mru_prev = client->mru_prev;
    }
    if (mgr->mru_tail == client) {
        mgr->mru_tail = client->mru_prev;
    }
    
    /* Insert at head */
    client->mru_prev = NULL;
    client->mru_next = mgr->mru_head;
    if (mgr->mru_head) {
        mgr->mru_head->mru_prev = client;
    }
    mgr->mru_head = client;
}

AbstractClient* wm_client_manager_get_focused(const WmClientManager *mgr) {
    return mgr ? mgr->focused : NULL;
}

void wm_client_manager_set_focused(WmClientManager *mgr, AbstractClient *client) {
    if (!mgr) return;
    mgr->focused = client;
}
