<?php

namespace Storeplugin\PDFInvoice\Framework\Abstracts;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Contracts\DataSourceInterface;

/**
 * Abstract Data Source with automatic position management
 *
 * Developers only need to implement:
 * - getRawData() - Return array of items from storage
 * - saveRawData($items) - Save array of items to storage
 *
 * Everything else (position management, sorting, pagination) is handled automatically
 */
abstract class AbstractDataSource implements DataSourceInterface {
    /**
     * Get raw data from storage
     *
     * @return array Array of items
     */
    abstract protected function getRawData();

    /**
     * Save raw data to storage
     *
     * @param array $items Array of items to save
     * @return bool True on success, false on failure
     */
    abstract protected function saveRawData( array $items );

    /**
     * Get the ID field name (override if your ID field is not 'id')
     *
     * @return string
     */
    protected function getIdField() {
        return 'id';
    }

    /**
     * Get the field name used by list-table row attributes.
     *
     * @return string
     */
    public function getRowIdField(): string {
        return (string) $this->getIdField();
    }

    /**
     * Normalize data - automatically add positions if missing
     *
     * @param array $items Raw items
     * @return array Normalized items with positions
     */
    protected function normalizeData( array $items ) {
        $idField = $this->getIdField();
        $needsSave = false;

        foreach ( $items as $index => &$item ) {
            // Ensure item is an array
            if ( is_object( $item ) ) {
                $item = (array) $item;
            }

            // Auto-add position if missing
            if ( ! isset( $item['position'] ) ) {
                $item['position'] = $index + 1;
                $needsSave = true;
            }

            // Ensure ID exists
            if ( ! isset( $item[ $idField ] ) ) {
                // Auto-generate ID if missing
                $item[ $idField ] = $this->generateId( $items );
                $needsSave = true;
            }
        }

        // Save if we added any positions or IDs
        if ( $needsSave ) {
            $this->saveRawData( $items );
        }

        return $items;
    }

    /**
     * Generate a unique ID for a new item
     *
     * @param array $items Existing items
     * @return int
     */
    protected function generateId( array $items ) {
        $idField = $this->getIdField();
        $maxId = 0;

        foreach ( $items as $item ) {
            $itemArray = (array) $item;
            if ( isset( $itemArray[ $idField ] ) && $itemArray[ $idField ] > $maxId ) {
                $maxId = $itemArray[ $idField ];
            }
        }

        return $maxId + 1;
    }

    /**
     * Get all items (normalized with positions)
     *
     * @return array
     */
    protected function getAllItems() {
        $items = $this->getRawData();
        return $this->normalizeData( $items );
    }

    /**
     * Get items with pagination, sorting, and search
     *
     * @param int    $per_page
     * @param int    $page_number
     * @param string $orderby
     * @param string $order
     * @param string $search
     * @return array
     */
    public function getItems( $per_page, $page_number, $orderby, $order, $search ) {
        $items = $this->getAllItems();

        // Apply search filter
        if ( ! empty( $search ) ) {
            $items = $this->applySearch( $items, $search );
        }

        // Apply sorting
        if ( ! empty( $orderby ) ) {
            $items = $this->applySort( $items, $orderby, $order );
        } else {
            // Default: sort by position (for drag and drop)
            $items = $this->sortByPosition( $items );
        }

        // Apply pagination
        $offset = ( $page_number - 1 ) * $per_page;
        return array_slice( $items, $offset, $per_page );
    }

    /**
     * Get total number of items
     *
     * @param string $search Search term
     * @return int
     */
    public function getTotalItems( $search ) {
        $items = $this->getAllItems();

        if ( ! empty( $search ) ) {
            $items = $this->applySearch( $items, $search );
        }

        return count( $items );
    }

    /**
     * Delete items by IDs
     *
     * @param array $ids Array of IDs to delete
     * @return bool
     */
    public function deleteItems( array $ids ) {
        $items = $this->getAllItems();
        $idField = $this->getIdField();

        // Filter out items with IDs in the delete list
        $items = array_filter( $items, function ( $item ) use ( $ids, $idField ) {
            $itemArray = (array) $item;
            $itemId = $itemArray[ $idField ] ?? null;
            return ! in_array( $itemId, $ids );
        } );

        // Re-index array and update positions
        $items = array_values( $items );
        foreach ( $items as $index => &$item ) {
            $item['position'] = $index + 1;
        }

        return $this->saveRawData( $items );
    }

    /**
     * Update the order of items
     *
     * @param array $ordered_ids Array of IDs in new order
     * @return bool
     */
    public function updateOrder( array $orderedIds ) {
        $items = $this->getAllItems();
        $idField = $this->getIdField();

        // Create a map of ID => item
        $itemsMap = [];
        foreach ( $items as $item ) {
            $itemArray = (array) $item;
            $itemId = $itemArray[ $idField ] ?? null;
            if ( $itemId ) {
                $itemsMap[ $itemId ] = $item;
            }
        }

        // Reorder items based on orderedIds and update positions
        $reordered = [];
        $position = 1;

        foreach ( $orderedIds as $id ) {
            if ( isset( $itemsMap[ $id ] ) ) {
                $item = $itemsMap[ $id ];
                $item['position'] = $position++;
                $reordered[] = $item;
                unset( $itemsMap[ $id ] );
            }
        }

        // Append any remaining items (not in orderedIds)
        foreach ( $itemsMap as $item ) {
            $item['position'] = $position++;
            $reordered[] = $item;
        }

        return $this->saveRawData( $reordered );
    }

    /**
     * Apply search filter to items (override for custom search logic)
     *
     * Default: searches all string fields
     *
     * @param array  $items Items to search
     * @param string $search Search term
     * @return array Filtered items
     */
    protected function applySearch( array $items, $search ) {
        return array_filter( $items, function ( $item ) use ( $search ) {
            $itemArray = (array) $item;

            foreach ( $itemArray as $key => $value ) {
                // Skip non-string fields and internal fields
                if ( $key === 'position' || ! is_string( $value ) ) {
                    continue;
                }

                if ( stripos( $value, $search ) !== false ) {
                    return true;
                }
            }

            return false;
        } );
    }

    /**
     * Apply sorting to items
     *
     * @param array  $items Items to sort
     * @param string $orderby Column to sort by
     * @param string $order Sort direction (asc/desc)
     * @return array Sorted items
     */
    protected function applySort( array $items, $orderby, $order ) {
        usort( $items, function ( $firstItem, $secondItem ) use ( $orderby, $order ) {
            $firstItemArray = (array) $firstItem;
            $secondItemArray = (array) $secondItem;

            $firstItemValue = $firstItemArray[ $orderby ] ?? '';
            $secondItemValue = $secondItemArray[ $orderby ] ?? '';

            // Numeric comparison if both values are numeric
            if ( is_numeric( $firstItemValue ) && is_numeric( $secondItemValue ) ) {
                $result = $firstItemValue - $secondItemValue;
            } else {
                $result = strcmp( $firstItemValue, $secondItemValue );
            }

            return ( $order === 'asc' ) ? $result : -$result;
        } );

        return $items;
    }

    /**
     * Sort items by position
     *
     * @param array $items Items to sort
     * @return array Sorted items
     */
    protected function sortByPosition( array $items ) {
        usort( $items, function ( $firstItem, $secondItem ) {
            $firstItemArray = (array) $firstItem;
            $secondItemArray = (array) $secondItem;

            $firstItemPosition = $firstItemArray['position'] ?? 999999;
            $secondItemPosition = $secondItemArray['position'] ?? 999999;

            return $firstItemPosition - $secondItemPosition;
        } );

        return $items;
    }

    /**
     * Get views (default implementation - override if needed)
     *
     * @return array
     */
    public function getViews() {
        $totalCount = count( $this->getAllItems() );
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading current admin view/page from the URL to render list-table links; no state change.
        $current = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : 'all';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading current admin page slug from the URL to render list-table links; no state change.
        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';

        return [
            'all' => sprintf(
                '<a href="?page=%s&view=all" class="%s">All <span class="count">(%d)</span></a>',
                esc_attr( $page ),
                $current === 'all' ? 'current' : '',
                $totalCount
            ),
        ];
    }
}
