#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>

# This module must be runnable by a vanilla python interpreter
# as it is used to generate C code when building kitty

import re
import sys
from collections.abc import Iterator, Sequence
from enum import Enum, auto
from functools import lru_cache
from typing import Any, NamedTuple

if getattr(sys, 'running_from_setup', False):
    is_macos = 'darwin' in sys.platform.lower()
    from shlex import split as psplit

    def shlex_split(text: str) -> Iterator[str]:
        yield from psplit(text)
else:
    from kitty.constants import appname, is_macos
    from kitty.utils import shlex_split as ksplit
    def shlex_split(text: str) -> Iterator[str]:
        yield from ksplit(text)


def serialize_as_go_string(x: str) -> str:
    return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')


class CompletionType(Enum):
    file = auto()
    directory = auto()
    keyword = auto()
    special = auto()
    none = auto()


class CompletionRelativeTo(Enum):
    cwd = auto()
    config_dir = auto()


class CompletionSpec(NamedTuple):

    type: CompletionType = CompletionType.none
    kwds: tuple[str,...] = ()
    extensions: tuple[str,...] = ()
    mime_patterns: tuple[str,...] = ()
    group: str = ''
    relative_to: CompletionRelativeTo = CompletionRelativeTo.cwd

    @staticmethod
    def from_string(raw: str) -> 'CompletionSpec':
        typ = CompletionType.none
        kwds: tuple[str, ...] = ()
        extensions: tuple[str, ...] = ()
        mime_patterns: tuple[str, ...] = ()
        group = ''
        relative_to = CompletionRelativeTo.cwd
        for x in shlex_split(raw):
            ck, vv = x.split(':', 1)
            if ck == 'type':
                typ = getattr(CompletionType, vv)
            elif ck == 'kwds':
                kwds += tuple(vv.split(','))
            elif ck == 'ext':
                extensions += tuple(vv.split(','))
            elif ck == 'group':
                group = vv
            elif ck == 'mime':
                mime_patterns += tuple(vv.split(','))
            elif ck == 'relative':
                if vv == 'conf':
                    relative_to = CompletionRelativeTo.config_dir
                else:
                    raise ValueError(f'Unknown completion relative to value: {vv}')
            else:
                raise KeyError(f'Unknown completion property: {ck}')
        return CompletionSpec(
            type=typ, kwds=kwds, extensions=extensions, mime_patterns=mime_patterns, group=group, relative_to=relative_to)

    def as_go_code(self, go_name: str, sep: str = ': ') -> Iterator[str]:
        completers = []
        if self.kwds:
            kwds = (f'"{serialize_as_go_string(x)}"' for x in self.kwds)
            g = (self.group if self.type is CompletionType.keyword else '') or "Keywords"
            completers.append(f'cli.NamesCompleter("{serialize_as_go_string(g)}", ' + ', '.join(kwds) + ')')
        relative_to = 'CONFIG' if self.relative_to is CompletionRelativeTo.config_dir else 'CWD'
        if self.type is CompletionType.file:
            g = serialize_as_go_string(self.group or 'Files')
            added = False
            if self.extensions:
                added = True
                pats = (f'"*.{ext}"' for ext in self.extensions)
                completers.append(f'cli.FnmatchCompleter("{g}", cli.{relative_to}, ' + ', '.join(pats) + ')')
            if self.mime_patterns:
                added = True
                completers.append(f'cli.MimepatCompleter("{g}", cli.{relative_to}, ' + ', '.join(f'"{p}"' for p in self.mime_patterns) + ')')
            if not added:
                completers.append(f'cli.FnmatchCompleter("{g}", cli.{relative_to}, "*")')
        if self.type is CompletionType.directory:
            g = serialize_as_go_string(self.group or 'Directories')
            completers.append(f'cli.DirectoryCompleter("{g}", cli.{relative_to})')
        if self.type is CompletionType.special:
            completers.append(self.group)
        if len(completers) > 1:
            yield f'{go_name}{sep}cli.ChainCompleters(' + ', '.join(completers) + ')'
        elif completers:
            yield f'{go_name}{sep}{completers[0]}'


class OptionDefinition(NamedTuple):
    dest: str = ''
    name: str = ''
    aliases: tuple[str, ...] = ()
    help: str = ''
    choices: tuple[str, ...] = ()
    type: str = ''
    default: str | None = None
    condition: bool = False
    completion: CompletionSpec = CompletionSpec()



OptionSpecSeq = Sequence[str | OptionDefinition]


@lru_cache(64)
def parse_option_spec(spec: str | None = None) -> tuple[OptionSpecSeq, OptionSpecSeq]:
    if spec is None:
        spec = kitty_options_spec()
    NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
    state = NORMAL
    lines = spec.splitlines()
    prev_line = ''
    prev_indent = 0
    seq: list[str | OptionDefinition] = []
    disabled: list[str | OptionDefinition] = []
    mpat = re.compile('([a-z]+)=(.+)')
    current_cmd = empty_cmd = OptionDefinition()

    def indent_of_line(x: str) -> int:
        return len(x) - len(x.lstrip())

    for line in lines:
        line = line.rstrip()
        if state is NORMAL:
            if not line:
                continue
            if line.startswith('# '):
                seq.append(line[2:])
                continue
            if line.startswith('--'):
                parts = line.split(' ')
                defdest = parts[0][2:].replace('-', '_')
                current_cmd = OptionDefinition(dest=defdest, aliases=tuple(parts), name=defdest, condition=True)
                state = METADATA
                continue
            raise ValueError(f'Invalid option spec, unexpected line: {line}')
        elif state is METADATA:
            m = mpat.match(line)
            if m is None:
                state = HELP
                current_cmd = current_cmd._replace(help=current_cmd.help + line)
            else:
                k, v = m.group(1), m.group(2)
                if k == 'choices':
                    vals = tuple(x.strip() for x in v.split(','))
                    if not current_cmd.type:
                        current_cmd = current_cmd._replace(type='choices')
                    if current_cmd.type != 'choices':
                        raise ValueError(f'Cannot specify choices for an option of type: {current_cmd.type}')
                    current_cmd = current_cmd._replace(choices=tuple(vals))
                    if current_cmd.default is None:
                        current_cmd = current_cmd._replace(default=vals[0])
                else:
                    if k == 'default':
                        current_cmd = current_cmd._replace(default=v)
                    elif k == 'type':
                        if v == 'choice':
                            v = 'choices'
                        current_cmd = current_cmd._replace(type=v)
                    elif k == 'dest':
                        current_cmd = current_cmd._replace(dest=v)
                    elif k == 'condition':
                        current_cmd = current_cmd._replace(condition=bool(eval(v)))
                    elif k == 'completion':
                        current_cmd = current_cmd._replace(completion=CompletionSpec.from_string(v))
        elif state is HELP:
            if line:
                current_indent = indent_of_line(line)
                if current_indent > 1:
                    if prev_indent == 0:
                        current_cmd = current_cmd._replace(help=current_cmd.help + '\n')
                    else:
                        line = line.strip()
                prev_indent = current_indent
                spc = '' if current_cmd.help.endswith('\n') else ' '
                current_cmd = current_cmd._replace(help=current_cmd.help + spc + line)
            else:
                prev_indent = 0
                if prev_line:
                    h = '\n' if current_cmd.help.endswith('::') else '\n\n'
                    current_cmd = current_cmd._replace(help=current_cmd.help + h)
                else:
                    state = NORMAL
                    (seq if current_cmd.condition else disabled).append(current_cmd)
                    current_cmd = empty_cmd
        prev_line = line
    if current_cmd is not empty_cmd:
        (seq if current_cmd.condition else disabled).append(current_cmd)

    return seq, disabled


def defval_for_opt(opt: OptionDefinition) -> Any:
    dv: Any = opt.default
    typ = opt.type
    if typ.startswith('bool-'):
        if dv is None:
            dv = False if typ == 'bool-set' else True
        else:
            dv = dv.lower() in ('true', 'yes', 'y')
    elif typ == 'list':
        dv = list(shlex_split(dv)) if dv else []
    elif typ in ('int', 'float'):
        dv = (int if typ == 'int' else float)(dv or 0)
    return dv


def get_option_maps(seq: OptionSpecSeq) -> tuple[dict[str, OptionDefinition], dict[str, OptionDefinition], dict[str, Any]]:
    names_map: dict[str, OptionDefinition] = {}
    alias_map: dict[str, OptionDefinition] = {}
    values_map: dict[str, Any] = {}
    for opt in seq:
        if isinstance(opt, str):
            continue
        for alias in opt.aliases:
            alias_map[alias] = opt
        name = opt.dest
        names_map[name] = opt
        values_map[name] = defval_for_opt(opt)
    return names_map, alias_map, values_map


def c_str(x: str) -> str:
    x = x.replace('\\', r'\\')
    return f'"{x}"'


def add_list_values(*values: str) -> Iterator[str]:
    yield f'\tflag.defval.listval.items = alloc_for_cli(spec, {len(values)} * sizeof(flag.defval.listval.items[0]));'
    yield '\tif (!flag.defval.listval.items) OOM;'
    yield f'\tflag.defval.listval.count = {len(values)};'
    yield f'\tflag.defval.listval.capacity = {len(values)};'
    for n, value in enumerate(values):
        yield f'\tflag.defval.listval.items[{n}] = {c_str(value)};'


def generate_c_for_opt(name: str, defval: Any, opt: OptionDefinition) -> Iterator[str]:
    yield f'\tflag = (FlagSpec){{.dest={c_str(name)},}};'
    match opt.type:
        case 'bool-set' | 'bool-reset':
            yield '\tflag.defval.type = CLI_VALUE_BOOL;'
            yield f'\tflag.defval.boolval = {"true" if defval else "false"};'
        case 'int':
            yield '\tflag.defval.type = CLI_VALUE_INT;'
            yield f'\tflag.defval.intval = {defval};'
        case 'float':
            yield '\tflag.defval.type = CLI_VALUE_FLOAT;'
            yield f'\tflag.defval.floatval = {defval};'
        case 'list':
            yield '\tflag.defval.type = CLI_VALUE_LIST;'
            if defval:
                yield from add_list_values(*defval)
        case 'choices':
            yield '\tflag.defval.type = CLI_VALUE_CHOICE;'
            yield f'\tflag.defval.strval = {c_str(defval)};'
            yield from add_list_values(*opt.choices)
        case _:
            yield '\tflag.defval.type = CLI_VALUE_STRING;'
            if defval is not None:
                yield f'\tflag.defval.strval = {c_str(defval)};'


def generate_c_parser_for(funcname: str, spec: str) -> Iterator[str]:
    seq, disabled = parse_option_spec(spec)
    names_map, _, defaults_map = get_option_maps(seq)
    if 'help' not in names_map:
        names_map['help'] = OptionDefinition(type='bool-set', aliases=('--help', '-h'))
        defaults_map['help'] = False
    if 'version' not in names_map:
        names_map['version'] = OptionDefinition(type='bool-set', aliases=('--version', '-v'))
        defaults_map['version'] = False

    yield f'static void\nparse_cli_for_{funcname}(CLISpec *spec, int argc, char **argv) {{'  # }}
    yield '\tFlagSpec flag;'
    for name, opt in names_map.items():
        for alias in opt.aliases:
            yield f'\tif (vt_is_end(vt_insert(&spec->alias_map, {c_str(alias)}, {c_str(name)}))) OOM;'
        yield from generate_c_for_opt(name, defaults_map[name], opt)
        yield '\tif (vt_is_end(vt_insert(&spec->flag_map, flag.dest, flag))) OOM;'
    for d in disabled:
        if not isinstance(d, str):
            yield from generate_c_for_opt(d.dest, defval_for_opt(d), d)
            yield '\tif (vt_is_end(vt_insert(&spec->disabled_map, flag.dest, flag))) OOM;'

    yield '\tparse_cli_loop(spec, true, argc, argv);'
    yield '}'


def generate_c_parsers() -> Iterator[str]:
    yield '#pragma once'
    yield '// generated by simple_cli_definitions.py do NOT edit!'
    yield '#include "cli-parser.h"'
    yield from generate_c_parser_for('kitty', kitty_options_spec())
    yield ''
    yield ''
    yield from generate_c_parser_for('panel_kitten', build_panel_cli_spec({}))
    yield ''


# kitty CLI spec {{{
grab_keyboard_docs = """\
Grab the keyboard. This means global shortcuts defined in the OS will be passed to kitty instead. Useful if
you want to create an OS modal window. How well this
works depends on the OS/window manager/desktop environment. On Wayland it works only if the compositor implements
the :link:`inhibit-keyboard-shortcuts protocol <https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1>`.
On macOS Apple doesn't allow applications to grab the keyboard without special permissions, so it doesn't work.
"""

listen_on_defn = f'''\
--listen-on
completion=type:special group:complete_kitty_listen_on
Listen on the specified socket address for control messages. For example,
:option:`{appname} --listen-on`=unix:/tmp/mykitty or :option:`{appname}
--listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract
UNIX sockets, not associated with a file, like this: :option:`{appname}
--listen-on`=unix:@mykitty. Environment variables are expanded and relative
paths are resolved with respect to the temporary directory. To control kitty,
you can send commands to it with :italic:`kitten @` using the
:option:`kitten @ --to` option to specify this address. Note that if you run
:italic:`kitten @` within a kitty window, there is no need to specify the
:option:`kitten @ --to` option as it will automatically read from the
environment. Note that this will be ignored unless :opt:`allow_remote_control`
is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. This can
also be specified in :file:`kitty.conf`.
'''

wait_for_single_instance_defn = f'''\
--wait-for-single-instance-window-close
type=bool-set
Normally, when using :option:`{appname} --single-instance`, :italic:`{appname}`
will open a new window in an existing instance and quit immediately. With this
option, it will not quit till the newly opened window is closed. Note that if no
previous instance is found, then :italic:`{appname}` will wait anyway,
regardless of this option.
'''

CONFIG_HELP = '''\
Specify a path to the configuration file(s) to use. All configuration files are
merged onto the builtin :file:`{conf_name}.conf`, overriding the builtin values.
This option can be specified multiple times to read multiple configuration files
in sequence, which are merged. Use the special value :code:`NONE` to not load
any config file.

If this option is not specified, config files are searched for in the order:
:file:`$XDG_CONFIG_HOME/{appname}/{conf_name}.conf`,
:file:`~/.config/{appname}/{conf_name}.conf`,{macos_confpath}
:file:`$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf`. The first one that exists
is used as the config file.

If the environment variable :envvar:`KITTY_CONFIG_DIRECTORY` is specified, that
directory is always used and the above searching does not happen.

If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists, it is merged before (i.e.
with lower priority) than any user config files. It can be used to specify
system-wide defaults for all users. You can use either :code:`-` or
:file:`/dev/stdin` to read the config from STDIN.
'''.replace(
    '{macos_confpath}',
    (' :file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
)


def kitty_options_spec() -> str:
    if not hasattr(kitty_options_spec, 'ans'):
        OPTIONS = """
--class --app-id
dest=cls
default={appname}
condition=not is_macos
On Wayland set the :italic:`application id`. On X11 set the class part of the :italic:`WM_CLASS` window property.


--name --os-window-tag
condition=not is_macos
On Wayland, set the :italic:`window tag`, when specified.
On X11, set the name part of the :italic:`WM_CLASS` property, when unset, defaults to using the
value from :option:`{appname} --class`.


--title -T
Set the OS window title. This will override any title set by the program running
inside kitty, permanently fixing the OS window's title. So only use this if you
are running a program that does not set titles.


--config -c
type=list
completion=type:file ext:conf group:"Config files" kwds:none,NONE
{config_help}


--override -o
type=list
completion=type:special group:complete_kitty_override
Override individual configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`{appname} -o` font_size=20


--directory --working-directory -d
default=.
completion=type:directory
Change to the specified directory when launching.


--detach
type=bool-set
Detach from the controlling terminal, if any. On macOS
use :code:`open -a kitty.app -n` instead.


--detached-log
Path to a log file to store STDOUT/STDERR when using :option:`--detach`


--session
completion=type:file ext:session relative:conf group:"Session files"
Path to a file containing the startup :italic:`session` (tabs, windows, layout,
programs). Use - to read from STDIN. See :ref:`sessions` for details and
an example. Environment variables in the file name are expanded,
relative paths are resolved relative to the kitty configuration directory.
The special value :code:`none` means no session will be used, even if
the :opt:`startup_session` option has been specified in kitty.conf.
Note that using this option means the command line arguments to kitty specifying
a program to run are ignored.


--hold
type=bool-set
Remain open, at a shell prompt, after child process exits. Note that this only
affects the first window. You can quit by either using the close window
shortcut or running the exit command.


--single-instance -1
type=bool-set
If specified only a single instance of :italic:`{appname}` will run. New
invocations will instead create a new top-level window in the existing
:italic:`{appname}` instance. This allows :italic:`{appname}` to share a single
sprite cache on the GPU and also reduces startup time. You can also have
separate groups of :italic:`{appname}` instances by using the :option:`{appname}
--instance-group` option. You can use the :option:`{appname} --start-as`=hidden
flag to start a background kitty instance that acts as a server.


--instance-group
Used in combination with the :option:`{appname} --single-instance` option. All
:italic:`{appname}` invocations with the same :option:`{appname}
--instance-group` will result in new windows being created in the first
:italic:`{appname}` instance within that group.


{wait_for_single_instance_defn}


{listen_on_defn} To start in headless mode,
without an actual window, use :option:`{appname} --start-as`=hidden.


--start-as
type=choices
default=normal
choices=normal,fullscreen,maximized,minimized,hidden
Control how the initial kitty OS window is created. Note that
this is applies to all OS Windows if you use the :option:`{appname} --session`
option to create multiple OS Windows. Any OS Windows state
specified in the session file gets overriden.


--position
The position, for example 10x20, on screen at which to place the first kitty OS Window
created by this invocation.
This may or may not work depending on the policies of the desktop
environment/window manager. It never works on Wayland.
See also :opt:`remember_window_position` to have kitty automatically try
to restore the previous window position.


--grab-keyboard
type=bool-set
{grab_keyboard_docs}


# Debugging options

--version -v
type=bool-set
The current {appname} version.


--dump-commands
type=bool-set
Output commands received from child process to STDOUT.


--replay-commands
Replay previously dumped commands. Specify the path to a dump file previously
created by :option:`{appname} --dump-commands`. You
can open a new kitty window to replay the commands with::

    {appname} sh -c "{appname} --replay-commands /path/to/dump/file; read"


--dump-bytes
Path to file in which to store the raw bytes received from the child process.


--debug-rendering --debug-gl
type=bool-set
Debug rendering commands. This will cause all OpenGL calls to check for errors
instead of ignoring them. Also prints out miscellaneous debug information.
Useful when debugging rendering problems.


--debug-input --debug-keyboard
dest=debug_keyboard
type=bool-set
Print out key and mouse events as they are received.


--debug-font-fallback
type=bool-set
Print out information about the selection of fallback fonts for characters not
present in the main font.


--watcher
completion=type:file ext:py relative:conf group:"Watcher files"
This option is deprecated in favor of the :opt:`watcher` option in
:file:`{conf_name}.conf` and should not be used.


--execute -e
type=bool-set
!
"""
        setattr(kitty_options_spec, 'ans', OPTIONS.format(
            appname=appname, conf_name=appname, listen_on_defn=listen_on_defn,
            grab_keyboard_docs=grab_keyboard_docs, wait_for_single_instance_defn=wait_for_single_instance_defn,
            config_help=CONFIG_HELP.format(appname=appname, conf_name=appname
        )))
    ans: str = getattr(kitty_options_spec, 'ans')
    return ans
# }}}


# panel CLI spec {{{
panel_defaults = {
    'lines': '1', 'columns': '1',
    'margin_left': '0', 'margin_top': '0', 'margin_right': '0', 'margin_bottom': '0',
    'edge': 'top', 'layer': 'bottom', 'override': '', 'cls': f'{appname}-panel',
    'focus_policy': 'not-allowed', 'exclusive_zone': '-1', 'override_exclusive_zone': 'no',
    'single_instance': 'no', 'instance_group': '', 'toggle_visibility': 'no',
    'start_as_hidden': 'no', 'detach': 'no', 'detached_log': '',
}

def build_panel_cli_spec(defaults: dict[str, str]) -> str:
    d = panel_defaults.copy()
    d.update(defaults)
    return r'''
--lines
default={lines}
The number of lines shown in the panel. Ignored for background, centered, and vertical panels.
If it has the suffix :code:`px` then it sets the height of the panel in pixels instead of lines.


--columns
default={columns}
The number of columns shown in the panel. Ignored for background, centered, and horizontal panels.
If it has the suffix :code:`px` then it sets the width of the panel in pixels instead of columns.


--margin-top
type=int
default={margin_top}
Set the top margin for the panel, in pixels. Has no effect for bottom edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.


--margin-left
type=int
default={margin_left}
Set the left margin for the panel, in pixels. Has no effect for right edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.


--margin-bottom
type=int
default={margin_bottom}
Set the bottom margin for the panel, in pixels. Has no effect for top edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.


--margin-right
type=int
default={margin_right}
Set the right margin for the panel, in pixels. Has no effect for left edge panels.
Only works on macOS and Wayland compositors that supports the wlr layer shell protocol.


--edge
choices=top,bottom,left,right,background,center,center-sized,none
default={edge}
Which edge of the screen to place the panel on. Note that some window managers
(such as i3) do not support placing docked windows on the left and right edges.
The value :code:`background` means make the panel the "desktop wallpaper".
Note that when using sway if you set a background in your sway config it will
cover the background drawn using this kitten.
Additionally, there are three more values: :code:`center`, :code:`center-sized` and :code:`none`.
The value :code:`center` anchors the panel to all sides and covers the entire
display (on macOS the part of the display not covered by titlebar and dock).
The panel can be shrunk and placed using the margin parameters.
The value :code:`none` anchors the panel to the top left corner and should be
placed using the margin parameters. Its size is set by :option:`--lines`
and :option:`--columns`. The value :code:`center-sized` is just like :code:`none` except
that the panel is centered instead of in the top left corner and the margins have no effect.


--layer
choices=background,bottom,top,overlay
default={layer}
On a Wayland compositor that supports the wlr layer shell protocol, specifies the layer
on which the panel should be drawn. This parameter is ignored and set to
:code:`background` if :option:`--edge` is set to :code:`background`. On macOS, maps
these to appropriate NSWindow *levels*. Note that on macOS, you can use :opt:`macos_ns_window_layer`
for more fine control over the layer, using the :code:`--override` flag, for example:
:code:`--override macos_ns_window_layer=NSPopUpMenuWindowLevel`.


--config -c
type=list
Path to config file to use for kitty when drawing the panel.


--override -o
type=list
default={override}
Override individual kitty configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20


--output-name
The panel can only be displayed on a single monitor (output) at a time. This allows
you to specify which output is used, by name. If not specified the compositor will choose an
output automatically, typically the last output the user interacted with or the primary monitor.
Use the special value :code:`list` to get a list of available outputs. Use :code:`listjson` for
a json encoded output. Note that on Wayland the output can only be set at panel creation time,
it cannot be changed after creation, nor is there anyway to display a single panel on all outputs.
Please complain to the Wayland developers about this.


--class --app-id
dest=cls
default={cls}
condition=not is_macos
On Wayland set the :italic:`namespace` of the layer shell surface. On X11 set the class part of the :italic:`WM_CLASS` window property.


--name --os-window-tag
condition=not is_macos
On X11 sets the name part of the :italic:`WM_CLASS` property on X11,
when unspecified uses the value from :option:`{appname} --class` on X11.


--focus-policy
choices=not-allowed,exclusive,on-demand
default={focus_policy}
On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard
interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details.
Note that different Wayland compositors behave very differently with :code:`exclusive`, your mileage may vary.
On macOS, :code:`exclusive` and :code:`on-demand` are currently the same.


--hide-on-focus-loss
type=bool-set
Automatically hide the panel window when it loses focus. Using this option will force :option:`--focus-policy`
to :code:`on-demand`. Note that on Wayland, depending on the compositor, this can result in the window never
becoming visible.


--grab-keyboard
type=bool-set
{grab_keyboard_docs}


--exclusive-zone
type=int
default={exclusive_zone}
On a Wayland compositor that supports the wlr layer shell protocol, request a given exclusive zone for the panel.
Please refer to the wlr layer shell documentation for more details on the meaning of exclusive and its value.
If :option:`--edge` is set to anything other than :code:`center` or :code:`none`, this flag will not have any
effect unless the flag :option:`--override-exclusive-zone` is also set.
If :option:`--edge` is set to :code:`background`, this option has no effect.
Ignored on X11 and macOS.


--override-exclusive-zone
type=bool-set
default={override_exclusive_zone}
On a Wayland compositor that supports the wlr layer shell protocol, override the default exclusive zone.
This has effect only if :option:`--edge` is set to :code:`top`, :code:`left`, :code:`bottom` or :code:`right`.
Ignored on X11 and macOS.


--single-instance -1
type=bool-set
default={single_instance}
If specified only a single instance of the panel will run. New
invocations will instead create a new top-level window in the existing
panel instance.


--instance-group
default={instance_group}
Used in combination with the :option:`--single-instance` option. All
panel invocations with the same :option:`--instance-group` will result
in new panels being created in the first panel instance within that group.


{wait_for_single_instance_defn}


{listen_on_defn}


--toggle-visibility
type=bool-set
default={toggle_visibility}
When set and using :option:`--single-instance` will toggle the visibility of the
existing panel rather than creating a new one.


--move-to-active-monitor
type=bool-set
default=false
When set and using :option:`--toggle-visibility` to show an existing panel, the panel
is moved to the active monitor (typically the monitor with the mouse on it).
This works only if the underlying OS supports it. It is currently supported on macOS only.


--start-as-hidden
type=bool-set
default={start_as_hidden}
Start in hidden mode, useful with :option:`--toggle-visibility`.


--detach
type=bool-set
default={detach}
Detach from the controlling terminal, if any, running in an independent child process,
the parent process exits immediately.


--detached-log
default={detached_log}
Path to a log file to store STDOUT/STDERR when using :option:`--detach`


--debug-rendering
type=bool-set
For internal debugging use.


--debug-input
type=bool-set
For internal debugging use.
'''.format(
    appname=appname, listen_on_defn=listen_on_defn, grab_keyboard_docs=grab_keyboard_docs,
    wait_for_single_instance_defn=wait_for_single_instance_defn, **d)


def panel_options_spec() -> str:
    return build_panel_cli_spec(panel_defaults)

# }}}
