<?php

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

class FormBuilder {

    private $items = [];
    private $hiddenitems = [];
    private $title = "";
    private $icon = "";
    private $buttons = [];
    private $action = "action.php";
    private $method = "POST";
    private $id = "editform";

    /**
     * Create a form with autogenerated HTML.
     *
     * @param string $title Form title/heading
     * @param string $icon FontAwesone icon next to the title.
     * @param string $action URL to submit the form to.
     * @param string $method Form submission method (POST, GET, etc.)
     */
    public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
        $this->title = $title;
        $this->icon = $icon;
        $this->action = $action;
        $this->method = $method;
    }

    /**
     * Set the title of the form.
     * @param string $title
     */
    public function setTitle(string $title) {
        $this->title = $title;
    }

    /**
     * Set the icon for the form.
     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
     */
    public function setIcon(string $icon) {
        $this->icon = $icon;
    }

    /**
     * Set the URL the form will submit to.
     * @param string $action
     */
    public function setAction(string $action) {
        $this->action = $action;
    }

    /**
     * Set the form submission method (GET, POST, etc)
     * @param string $method
     */
    public function setMethod(string $method = "POST") {
        $this->method = $method;
    }

    /**
     * Set the form ID.
     * @param string $id
     */
    public function setID(string $id = "editform") {
        $this->id = $id;
    }

    /**
     * Add an input to the form.
     *
     * @param string $name Element name
     * @param string $value Element value
     * @param string $type Input type (text, number, date, select, tel...)
     * @param bool $required If the element is required for form submission.
     * @param string $id Element ID
     * @param array $options Array of [value => text] pairs for a select element
     * @param string $label Text label to display near the input
     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
     * @param int $width Bootstrap column width for the input, out of 12.
     * @param int $minlength Minimum number of characters for the input.
     * @param int $maxlength Maximum number of characters for the input.
     * @param string $pattern Regex pattern for custom client-side validation.
     * @param string $error Message to show if the input doesn't validate.
     */
    public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
        $item = [
            "name" => $name,
            "value" => $value,
            "type" => $type,
            "required" => $required,
            "label" => $label,
            "icon" => $icon,
            "width" => $width,
            "minlength" => $minlength,
            "maxlength" => $maxlength
        ];
        if (!empty($id)) {
            $item["id"] = $id;
        }
        if (!empty($options) && $type == "select") {
            $item["options"] = $options;
        }
        if (!empty($pattern)) {
            $item["pattern"] = $pattern;
        }
        if (!empty($error)) {
            $item["error"] = $error;
        }
        $this->items[] = $item;
    }

    /**
     * Add a text input.
     *
     * @param string $name Element name
     * @param string $value Element value
     * @param bool $required If the element is required for form submission.
     * @param string $id Element ID
     * @param string $label Text label to display near the input
     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
     * @param int $width Bootstrap column width for the input, out of 12.
     * @param int $minlength Minimum number of characters for the input.
     * @param int $maxlength Maximum number of characters for the input.
     * @param string $pattern Regex pattern for custom client-side validation.
     * @param string $error Message to show if the input doesn't validate.
     */
    public function addTextInput(string $name, string $value = "", bool $required = true, string $id = "", string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
        $this->addInput($name, $value, "text", $required, $id, null, $label, $icon, $width, $minlength, $maxlength, $pattern, $error);
    }

    /**
     * Add a select dropdown.
     *
     * @param string $name Element name
     * @param string $value Element value
     * @param bool $required If the element is required for form submission.
     * @param string $id Element ID
     * @param array $options Array of [value => text] pairs for a select element
     * @param string $label Text label to display near the input
     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
     * @param int $width Bootstrap column width for the input, out of 12.
     */
    public function addSelect(string $name, string $value = "", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4) {
        $this->addInput($name, $value, "select", $required, $id, $options, $label, $icon, $width);
    }

    /**
     * Add a button to the form.
     *
     * @param string $text Text string to show on the button.
     * @param string $icon FontAwesome icon to show next to the text.
     * @param string $href If not null, the button will actually be a hyperlink.
     * @param string $type Usually "button" or "submit".  Ignored if $href is set.
     * @param string $id The element ID.
     * @param string $name The element name for the button.
     * @param string $value The form value for the button. Ignored if $name is null.
     * @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
     */
    public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
        $button = [
            "text" => $text,
            "icon" => $icon,
            "class" => $class,
            "type" => $type,
            "id" => $id,
            "href" => $href,
            "name" => $name,
            "value" => $value
        ];
        $this->buttons[] = $button;
    }

    /**
     * Add a hidden input.
     * @param string $name
     * @param string $value
     */
    public function addHiddenInput(string $name, string $value) {
        $this->hiddenitems[$name] = $value;
    }

    /**
     * Generate the form HTML.
     * @param bool $echo If false, returns HTML string instead of outputting it.
     */
    public function generate(bool $echo = true) {
        $html = <<<HTMLTOP
<form action="$this->action" method="$this->method" id="$this->id">
    <div class="card">
         <h3 class="card-header d-flex">
            <div>
                <i class="$this->icon"></i> $this->title
            </div>
        </h3>

        <div class="card-body">
            <div class="row">
HTMLTOP;

        foreach ($this->items as $item) {
            $required = $item["required"] ? "required" : "";
            $id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
            $pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
            if (empty($item['type'])) {
                $item['type'] = "text";
            }
            $itemhtml = "";
            $itemlabel = "";

            if ($item['type'] == "textarea") {
                $itemlabel = "<label class=\"mb-0\"><i class=\"$item[icon]\"></i> $item[label]:</label>";
            } else if ($item['type'] != "checkbox") {
                $itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
            }
            $strippedlabel = strip_tags($item['label']);
            $itemhtml .= <<<ITEMTOP
\n\n                <div class="col-12 col-md-$item[width]">
                    <div class="form-group mb-3">
                        $itemlabel
ITEMTOP;
            $inputgrouptop = <<<INPUTG
\n                            <div class="input-group">
                            <div class="input-group-prepend">
                                <span class="input-group-text"><i class="$item[icon]"></i></span>
                            </div>
INPUTG;
            switch ($item['type']) {
                case "select":
                    $itemhtml .= $inputgrouptop;
                    $itemhtml .= <<<SELECT
\n                            <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
SELECT;
                    foreach ($item['options'] as $value => $label) {
                        $selected = "";
                        if (!empty($item['value']) && $value == $item['value']) {
                            $selected = " selected";
                        }
                        $itemhtml .= "\n                                <option value=\"$value\"$selected>$label</option>";
                    }
                    $itemhtml .= "\n                            </select>";
                    break;
                case "checkbox":
                    $itemhtml .= $inputgrouptop;
                    $itemhtml .= <<<CHECKBOX
\n                            <div class="form-group form-check">
                                <input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
                                <label class="form-check-label">$item[label]</label>
                              </div>
CHECKBOX;
                    break;
                case "textarea":
                    $val = htmlentities($item['value']);
                    $itemhtml .= <<<TEXTAREA
\n                            <textarea class="form-control" id="info" name="$item[name]" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $required>$val</textarea>
TEXTAREA;
                    break;
                default:
                    $itemhtml .= $inputgrouptop;
                    $itemhtml .= <<<INPUT
\n                            <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
INPUT;
                    break;
            }

            if (!empty($item["error"])) {
                $itemhtml .= <<<ERROR
\n                            <div class="invalid-feedback">
                                $item[error]
                            </div>
ERROR;
            }
            if ($item["type"] != "textarea") {
                $itemhtml .= "\n                                </div>";
            }
            $itemhtml .= <<<ITEMBOTTOM
\n                    </div>
                </div>\n
ITEMBOTTOM;
            $html .= $itemhtml;
        }

        $html .= <<<HTMLBOTTOM

            </div>
        </div>
HTMLBOTTOM;

        if (!empty($this->buttons)) {
            $html .= "\n        <div class=\"card-footer d-flex\">";
            foreach ($this->buttons as $btn) {
                $btnhtml = "";
                $inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
                $id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
                if (!empty($btn['href'])) {
                    $btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
                } else {
                    $name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
                    $value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
                    $btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
                }
                $html .= "\n            $btnhtml";
            }
            $html .= "\n        </div>";
        }

        $html .= "\n    </div>";
        foreach ($this->hiddenitems as $name => $value) {
            $value = htmlentities($value);
            $html .= "\n    <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
        }
        $html .= "\n</form>\n";

        if ($echo) {
            echo $html;
        }
        return $html;
    }

}
