<?php

namespace Storeplugin\PDFInvoice\Framework\ListTables;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Contracts\DataSourceInterface;
use Storeplugin\PDFInvoice\Framework\Contracts\TableInterface;
use Storeplugin\PDFInvoice\Config;
use Storeplugin\PDFInvoice\Asset;
use Exception;

class Table implements TableInterface {
    private $singular;
    private $plural;
    private $columns = [];
    private $sortableColumns = [];
    private $dataSource;
    private $bulkActions = [];
    private $bulkActionHandlers = [];
    private $searchEnabled = false;
    private $columnRenderers = [];
    private $rowActionsCallback;
    private $primaryColumn;
    private $hiddenColumns = [];
    private $perPage = 20;
    private $capability = 'manage_options';
    private $menuSlug;
    private $listTableInstance;
    private $menuType = null;
    private $menuArgs = [];
    private $submenuArgs = [];
    private $hideMenu = false;
    private $headerCallbacks = [];
    private $footerCallbacks = [];
    private $tableClasses = [];
    private $draggableEnabled = false;
    private $draggableConfig = [];
    private $pageTitle;
    private $description;

    /**
     * Constructor.
     *
     * @param string $singular
     * @param string $plural
     * @param string $menuSlug
     */
    private function __construct( $singular, $plural, $menuSlug = null ) {
        $this->singular = $singular;
        $this->plural = $plural;
        if ( $menuSlug !== null && $menuSlug !== '' ) {
            $this->menuSlug = sanitize_key( $menuSlug );
        } else {
            $this->menuSlug = $plural . '-list';
        }
    }

    /**
     * Create a new instance.
     *
     * @param string $singular
     * @param string $plural
     * @param string $menuSlug
     *
     * @return TableInterface
     */
    public static function create( $singular, $plural, $menuSlug = null ): TableInterface {
        return new self( $singular, $plural, $menuSlug );
    }

    /**
     * Set the columns.
     *
     * @param array $columns
     *
     * @return self
     */
    public function withColumns( array $columns ): self {
        $this->columns = $columns;
        return $this;
    }

    /**
     * Set the primary column.
     *
     * @param string $columnName
     *
     * @return self
     */
    public function withPrimaryColumn( $columnName ): self {
        $this->primaryColumn = $columnName;
        return $this;
    }

    /**
     * Set the sortable columns.
     *
     * @param array $sortable
     *
     * @return self
     */
    public function withSortableColumns( array $sortable ): self {
        $this->sortableColumns = $sortable;
        return $this;
    }

    /**
     * Set the data source.
     *
     * @param DataSourceInterface $dataSource
     *
     * @return self
     */
    public function fromDataSource( DataSourceInterface $dataSource ): self {
        $this->dataSource = $dataSource;
        return $this;
    }

    /**
     * Set the bulk actions.
     *
     * @param array $actions
     *
     * @return self
     */
    public function withBulkActions( array $actions ): self {
        $this->bulkActions = $actions;
        return $this;
    }

    /**
     * Add a bulk action handler.
     *
     * @param string   $action
     * @param callable $handler
     *
     * @return self
     */
    public function addBulkActionHandler( $action, callable $handler ): self {
        $this->bulkActionHandlers[ $action ] = $handler;
        return $this;
    }

    /**
     * Enable search.
     *
     * @param bool $columns
     *
     * @return self
     */
    public function withSearch( $columns = true ): self {
        $this->searchEnabled = true;
        return $this;
    }

    /**
     * Add a column renderer.
     *
     * @param string   $columnName
     * @param callable $callback
     *
     * @return self
     */
    public function addColumnRenderer( $columnName, callable $callback ): self {
        $this->columnRenderers[ $columnName ] = $callback;
        return $this;
    }

    /**
     * Set the row actions.
     *
     * @param callable $callback
     *
     * @return self
     */
    public function withRowActions( callable $callback ): self {
        $this->rowActionsCallback = $callback;
        return $this;
    }

    /**
     * Set the hidden columns.
     *
     * @param array $columns
     *
     * @return self
     */
    public function withHiddenColumns( array $columns ): self {
        $this->hiddenColumns = $columns;
        return $this;
    }

    /**
     * Set the pagination.
     *
     * @param int $perPage
     *
     * @return self
     */
    public function withPagination( $perPage ): self {
        $this->perPage = $perPage;
        return $this;
    }

    /**
     * Set the table classes.
     *
     * @param array $classes
     *
     * @return self
     */
    public function withTableClasses( array $classes ): self {
        $this->tableClasses = $classes;
        return $this;
    }

    /**
     * Set the capability.
     *
     * @param string $capability
     *
     * @return self
     */
    public function withCapability( $capability ): self {
        $this->capability = $capability;
        return $this;
    }

    /**
     * Set the description.
     *
     * @param string $description
     *
     * @return self
     */
    public function description( $description ): self {
        $this->description = $description;
        return $this;
    }

    /**
     * Set the admin menu.
     *
     * @param string      $pageTitle
     * @param string      $menuTitle
     * @param string      $iconUrl
     * @param int|null    $position
     *
     * @return self
     */
    public function withAdminMenu( $pageTitle, $menuTitle, $iconUrl = '', $position = null ): self {
        $this->pageTitle = $pageTitle;
        $this->menuType = 'menu';
        $this->menuArgs = [
            'page_title' => $pageTitle,
            'menu_title' => $menuTitle,
            'icon_url' => $iconUrl,
            'position' => $position,
        ];
        return $this;
    }

    /**
     * Hide the menu.
     *
     * @param bool $hide
     *
     * @return self
     */
    public function hideMenu( $hide = true ): self {
        $this->hideMenu = (bool) $hide;
        return $this;
    }

    /**
     * Set the admin submenu.
     *
     * @param string      $parentSlug
     * @param string      $pageTitle
     * @param string      $menuTitle
     * @param string|null $capability
     *
     * @return self
     */
    public function withAdminSubmenu( $parentSlug, $pageTitle, $menuTitle, $capability = null ): self {
        $this->pageTitle = $pageTitle;
        $this->menuType = 'submenu';
        $this->submenuArgs = [
            'parent_slug' => $parentSlug,
            'page_title' => $pageTitle,
            'menu_title' => $menuTitle,
            'capability' => $capability,
        ];
        return $this;
    }

    /**
     * Set the admin submenu for a post type.
     *
     * @param string      $postType
     * @param string|null $pageTitle
     * @param string|null $menuTitle
     * @param string|null $capability
     *
     * @return self
     */
    public function withAdminSubmenuPostType( $postType, $pageTitle = null, $menuTitle = null, $capability = null ): self {
        $postType = sanitize_key( $postType );
        if ( ! $postType ) {
            return $this;
        }

        if ( $pageTitle === null ) {
            $pageTitle = ucfirst( $this->plural );
        }

        $this->pageTitle = $pageTitle;

        if ( $menuTitle === null ) {
            $menuTitle = $pageTitle;
        }

        $parentSlug = $postType === 'post' ? 'edit.php' : 'edit.php?post_type=' . $postType;
        return $this->withAdminSubmenu( $parentSlug, $pageTitle, $menuTitle, $capability );
    }

    /**
     * Add a header callback.
     *
     * @param callable $callback
     *
     * @return self
     */
    public function addHeader( callable $callback ): self {
        $this->headerCallbacks[] = $callback;
        return $this;
    }

    /**
     * Add a footer callback.
     *
     * @param callable $callback
     *
     * @return self
     */
    public function addFooter( callable $callback ): self {
        $this->footerCallbacks[] = $callback;
        return $this;
    }

    /**
     * Enable drag and drop reordering.
     *
     * @param bool        $enabled       Whether to enable drag and drop
     * @param string|null $handleColumn Column to use as drag handle (null for auto-generated)
     *
     * @return self
     */
    public function draggable( $enabled = true, $handleColumn = null ): self {
        $this->draggableEnabled = (bool) $enabled;
        $this->draggableConfig = [
            'handle_column' => $handleColumn,
        ];
        return $this;
    }

    /**
     * Register a submenu page.
     *
     * @param string      $parentSlug
     * @param string      $pageTitle
     * @param string      $menuTitle
     * @param string|null $capability
     *
     * @return self
     */
    public function registerSubmenuPage( $parentSlug, $pageTitle, $menuTitle, $capability = null ) {
        $capability = $capability ?? $this->capability;

        add_submenu_page(
            $parentSlug,
            $pageTitle,
            $menuTitle,
            $capability,
            $this->menuSlug,
            [ $this, 'render' ]
        );

        // Add screen options hook
        add_action( 'load-' . $parentSlug, [ $this, 'maybeAddScreenOptions' ] );
        add_filter( 'set-screen-option', [ $this, 'saveScreenOptions' ], 10, 3 );

        return $this;
    }

    /**
     * Register a hidden page.
     *
     * @param string      $pageTitle
     * @param string      $menuTitle
     * @param string|null $capability
     *
     * @return string|false Hook suffix for the admin page, or false if the user does not have the capability.
     */
    private function registerHiddenPage( $pageTitle, $menuTitle, $capability = null ) {
        $capability = $capability ?? $this->capability;

        $hookSuffix = add_submenu_page(
            '',
            $pageTitle,
            $menuTitle,
            $capability,
            $this->menuSlug,
            [ $this, 'render' ]
        );

        if ( $hookSuffix ) {
            add_action( 'load-' . $hookSuffix, function () use ( $pageTitle ) {
                global $title;

                if ( empty( $title ) ) {
                    $title = $pageTitle;
                }
            } );
            add_action( 'load-' . $hookSuffix, [ $this, 'addScreenOptions' ] );
            add_filter( 'set-screen-option', [ $this, 'saveScreenOptions' ], 10, 3 );
        }

        return $this;
    }

    /**
     * Register a menu page.
     *
     * @param string      $pageTitle
     * @param string      $menuTitle
     * @param string      $iconUrl
     * @param int|null    $position
     *
     * @return self
     */
    public function registerMenuPage( $pageTitle, $menuTitle, $iconUrl = '', $position = null ) {
        add_menu_page(
            $pageTitle,
            $menuTitle,
            $this->capability,
            $this->menuSlug,
            [ $this, 'render' ],
            $iconUrl,
            $position
        );

        add_action( 'load-toplevel_page_' . $this->menuSlug, [ $this, 'addScreenOptions' ] );
        add_filter( 'set-screen-option', [ $this, 'saveScreenOptions' ], 10, 3 );

        return $this;
    }

    /**
     * Register the table with WordPress.
     *
     * @param int|null $priority Priority for the admin_menu hook. Lower numbers run earlier.
     *                      Parent menus should use lower priority than submenus.
     */
    public function register( $priority = null ): void {
        if ( $priority === null ) {
            $priority = ( $this->menuType === 'submenu' ) ? 20 : 10;
        }

        add_action( 'admin_menu', [ $this, 'registerAdminMenu' ], $priority );
    }

    /**
     * Register the admin menu.
     */
    public function registerAdminMenu() {
        if ( $this->hideMenu ) {
            $pageTitle = $this->submenuArgs['page_title'] ?? $this->menuArgs['page_title'] ?? ucfirst( $this->plural );
            $menuTitle = $this->submenuArgs['menu_title'] ?? $this->menuArgs['menu_title'] ?? $pageTitle;
            $capability = $this->submenuArgs['capability'] ?? $this->capability;

            $this->registerHiddenPage( $pageTitle, $menuTitle, $capability );

            return;
        }

        if ( $this->menuType === 'submenu' && ! empty( $this->submenuArgs ) ) {
            $this->registerSubmenuPage(
                $this->submenuArgs['parent_slug'],
                $this->submenuArgs['page_title'],
                $this->submenuArgs['menu_title'],
                $this->submenuArgs['capability']
            );

            return;
        }

        if ( $this->menuType === 'menu' && ! empty( $this->menuArgs ) ) {
            $this->registerMenuPage(
                $this->menuArgs['page_title'],
                $this->menuArgs['menu_title'],
                $this->menuArgs['icon_url'],
                $this->menuArgs['position']
            );
        }
    }

    /**
     * Conditionally add screen options.
     */
    public function maybeAddScreenOptions() {
        $screen = get_current_screen();
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a GET parameter used for screen options, not a form submission.
        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
        if ( $screen && $page === $this->menuSlug ) {
            $this->addScreenOptions();
        }
    }

    /**
     * Add screen options.
     */
    public function addScreenOptions() {
        $args = [
            'label'   => ucfirst( $this->plural ) . ' per page',
            'default' => $this->perPage,
            'option'  => $this->plural . '_per_page',
        ];

        add_screen_option( 'per_page', $args );
    }

    /**
     * Save screen options.
     *
     * @param mixed  $status
     * @param string $option
     * @param mixed  $value
     *
     * @return mixed
     */
    public function saveScreenOptions( $status, $option, $value ) {
        if ( $option === $this->plural . '_per_page' ) {
            return $value;
        }
        return $status;
    }

    /**
     * Validate the table configuration.
     *
     * @throws Exception
     */
    private function validate() {
        if ( empty( $this->columns ) ) {
            wp_die( 'List Table Error: Columns must be defined using withColumns()' );
        }

        if ( ! $this->dataSource ) {
            wp_die( 'List Table Error: Data source must be set using fromDataSource()' );
        }

        if ( ! $this->dataSource instanceof DataSourceInterface ) {
            wp_die( 'List Table Error: Data source must implement DataSourceInterface' );
        }
    }

    /**
     * Build the configuration array for the list table.
     *
     * @return array
     */
    private function buildConfig() {
        return [
            'singular'              => $this->singular,
            'plural'                => $this->plural,
            'columns'               => $this->columns,
            'sortable_columns'      => $this->sortableColumns,
            'bulk_actions'          => $this->bulkActions,
            'bulk_action_handlers'  => $this->bulkActionHandlers,
            'search_enabled'        => $this->searchEnabled,
            'column_renderers'      => $this->columnRenderers,
            'row_actions_callback'  => $this->rowActionsCallback,
            'primary_column'        => $this->primaryColumn ?? array_key_first( $this->columns ),
            'hidden_columns'        => $this->hiddenColumns,
            'per_page'              => $this->perPage,
            'table_classes'         => $this->tableClasses,
            'draggable_enabled'     => $this->draggableEnabled,
            'draggable_config'      => $this->draggableConfig,
            'menu_slug'             => $this->menuSlug,
        ];
    }

    /**
     * Render the table.
     */
    public function render() {
        $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $postType = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $orderBy = isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $order = isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $searchQuery = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        $this->validate();

        if ( ! $this->listTableInstance ) {
            $config = $this->buildConfig();
            $this->listTableInstance = new ListTable( $config, $this->dataSource );
        }

        // Register with DraggableRegistry if draggable is enabled
        if ( $this->draggableEnabled ) {
            DraggableRegistry::register(
                $this->menuSlug,
                $this->dataSource,
                array_merge( $this->draggableConfig, [
                    'capability' => $this->capability,
                ] )
            );
        }

        $this->listTableInstance->prepare_items();
        Asset::registerFrameworkAssets();
        wp_enqueue_style('dashicons');
        wp_enqueue_style(Config::asset('framework'));

        ?>
        <div class="nxtf-page-wrapper">
            <div class="nxtf-page-inner">
                <!-- Header Section -->
                <header class="nxtf-page-header">
                    <div>
                        <h1 class="nxtf-page-title"><?php echo esc_html( $this->pageTitle ); ?></h1>
                        <?php if ( $this->description ) : ?>
                            <p class="nxtf-page-description"><?php echo esc_html( $this->description ); ?></p>
                        <?php endif; ?>
                    </div>

                    <?php if ( $this->searchEnabled ) : ?>
                        <form method="get" class="nxtf-search-form" role="search">
                            <?php if ( $page ) : ?>
                                <input type="hidden" name="page" value="<?php echo esc_attr( $page ); ?>">
                            <?php endif; ?>
                            <?php if ( $postType ) : ?>
                                <input type="hidden" name="post_type" value="<?php echo esc_attr( $postType ); ?>">
                            <?php endif; ?>
                            <?php if ( $orderBy ) : ?>
                                <input type="hidden" name="orderby" value="<?php echo esc_attr( $orderBy ); ?>">
                            <?php endif; ?>
                            <?php if ( $order ) : ?>
                                <input type="hidden" name="order" value="<?php echo esc_attr( $order ); ?>">
                            <?php endif; ?>
                            <input type="hidden" name="paged" value="1">

                            <div class="nxtf-search-box">
                                <span class="dashicons dashicons-search nxtf-search-icon" aria-hidden="true"></span>
                                <label class="nxtf-sr-only" for="<?php echo esc_attr( $this->plural ); ?>-search-input">
                                    <?php echo esc_html( sprintf( 'Search %s', ucfirst( $this->plural ) ) ); ?>
                                </label>
                                <input
                                    id="<?php echo esc_attr( $this->plural ); ?>-search-input"
                                    name="s"
                                    type="search"
                                    value="<?php echo esc_attr( $searchQuery ); ?>"
                                    class="nxtf-search-input"
                                    placeholder="Search"
                                />
                                <button
                                    type="submit"
                                    class="nxtf-search-btn"
                                >
                                    <span class="dashicons dashicons-arrow-right-alt nxtf-search-btn-icon" aria-hidden="true"></span>
                                    <span>Search</span>
                                </button>
                            </div>
                        </form>
                    <?php endif; ?>
                </header>

                <?php if ( isset( $_GET['deleted'] ) ) : // phpcs:ignore WordPress.Security.NonceVerification.Recommended ?>
                    <div id="message" class="updated notice is-dismissible">
                        <p><?php esc_html_e( 'Items deleted successfully.', 'storeinvoice-print-pdf-invoices-and-packing-slips' ); ?></p>
                    </div>
                <?php endif; ?>

                <?php
                // Execute header callbacks
                foreach ( $this->headerCallbacks as $callback ) {
                    call_user_func( $callback );
                }
                ?>

                <!-- Table Content -->
                <div class="nxtf-table-container">
                    <div class="nxtf-table-scroll">
                        <form method="post" class="nxtf-table-form">
                            <?php $this->listTableInstance->display(); ?>
                        </form>
                    </div>
                </div>

                <?php
                foreach ( $this->footerCallbacks as $callback ) {
                    call_user_func( $callback );
                }
                ?>
            </div>
        </div>
        <?php
    }

    /**
     * Build and return a ListTable instance.
     *
     * @return ListTable
     */
    public function build() {
        $this->validate();
        return new ListTable( $this->buildConfig(), $this->dataSource );
    }
}
